Detailed changes
@@ -215,9 +215,9 @@ dependencies = [
[[package]]
name = "agent-client-protocol"
-version = "0.7.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525705e39c11cd73f7bc784e3681a9386aa30c8d0630808d3dc2237eb4f9cb1b"
+checksum = "3e639d6b544ad39f5b4e05802db5eb04e1518284eb05fda1839931003e0244c8"
dependencies = [
"agent-client-protocol-schema",
"anyhow",
@@ -226,16 +226,15 @@ dependencies = [
"derive_more 2.0.1",
"futures 0.3.31",
"log",
- "parking_lot",
"serde",
"serde_json",
]
[[package]]
name = "agent-client-protocol-schema"
-version = "0.6.2"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecf16c18fea41282d6bbadd1549a06be6836bddb1893f44a6235f340fa24e2af"
+checksum = "f182f5e14bef8232b239719bd99166bb11e986c08fc211f28e392f880d3093ba"
dependencies = [
"anyhow",
"derive_more 2.0.1",
@@ -439,7 +439,7 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
-agent-client-protocol = { version = "0.7.0", features = ["unstable"] }
+agent-client-protocol = { version = "=0.8.0", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"
@@ -201,17 +201,19 @@ impl ToolCall {
};
let mut content = Vec::with_capacity(tool_call.content.len());
for item in tool_call.content {
- content.push(ToolCallContent::from_acp(
+ if let Some(item) = ToolCallContent::from_acp(
item,
language_registry.clone(),
path_style,
terminals,
cx,
- )?);
+ )? {
+ content.push(item);
+ }
}
let result = Self {
- id: tool_call.id,
+ id: tool_call.tool_call_id,
label: cx
.new(|cx| Markdown::new(title.into(), Some(language_registry.clone()), None, cx)),
kind: tool_call.kind,
@@ -241,6 +243,7 @@ impl ToolCall {
locations,
raw_input,
raw_output,
+ ..
} = fields;
if let Some(kind) = kind {
@@ -262,21 +265,29 @@ impl ToolCall {
}
if let Some(content) = content {
- let new_content_len = content.len();
+ let mut new_content_len = content.len();
let mut content = content.into_iter();
// Reuse existing content if we can
for (old, new) in self.content.iter_mut().zip(content.by_ref()) {
- old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?;
+ let valid_content =
+ old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?;
+ if !valid_content {
+ new_content_len -= 1;
+ }
}
for new in content {
- self.content.push(ToolCallContent::from_acp(
+ if let Some(new) = ToolCallContent::from_acp(
new,
language_registry.clone(),
path_style,
terminals,
cx,
- )?)
+ )? {
+ self.content.push(new);
+ } else {
+ new_content_len -= 1;
+ }
}
self.content.truncate(new_content_len);
}
@@ -425,6 +436,7 @@ impl From<acp::ToolCallStatus> for ToolCallStatus {
acp::ToolCallStatus::InProgress => Self::InProgress,
acp::ToolCallStatus::Completed => Self::Completed,
acp::ToolCallStatus::Failed => Self::Failed,
+ _ => Self::Pending,
}
}
}
@@ -537,7 +549,7 @@ impl ContentBlock {
..
}) => Self::resource_link_md(&uri, path_style),
acp::ContentBlock::Image(image) => Self::image_md(&image),
- acp::ContentBlock::Audio(_) | acp::ContentBlock::Resource(_) => String::new(),
+ _ => String::new(),
}
}
@@ -591,15 +603,17 @@ impl ToolCallContent {
path_style: PathStyle,
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
cx: &mut App,
- ) -> Result<Self> {
+ ) -> Result<Option<Self>> {
match content {
- acp::ToolCallContent::Content { content } => Ok(Self::ContentBlock(ContentBlock::new(
- content,
- &language_registry,
- path_style,
- cx,
- ))),
- acp::ToolCallContent::Diff { diff } => Ok(Self::Diff(cx.new(|cx| {
+ acp::ToolCallContent::Content(acp::Content { content, .. }) => {
+ Ok(Some(Self::ContentBlock(ContentBlock::new(
+ content,
+ &language_registry,
+ path_style,
+ cx,
+ ))))
+ }
+ acp::ToolCallContent::Diff(diff) => Ok(Some(Self::Diff(cx.new(|cx| {
Diff::finalized(
diff.path.to_string_lossy().into_owned(),
diff.old_text,
@@ -607,12 +621,13 @@ impl ToolCallContent {
language_registry,
cx,
)
- }))),
- acp::ToolCallContent::Terminal { terminal_id } => terminals
+ })))),
+ acp::ToolCallContent::Terminal(acp::Terminal { terminal_id, .. }) => terminals
.get(&terminal_id)
.cloned()
- .map(Self::Terminal)
+ .map(|terminal| Some(Self::Terminal(terminal)))
.ok_or_else(|| anyhow::anyhow!("Terminal with id `{}` not found", terminal_id)),
+ _ => Ok(None),
}
}
@@ -623,9 +638,9 @@ impl ToolCallContent {
path_style: PathStyle,
terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,
cx: &mut App,
- ) -> Result<()> {
+ ) -> Result<bool> {
let needs_update = match (&self, &new) {
- (Self::Diff(old_diff), acp::ToolCallContent::Diff { diff: new_diff }) => {
+ (Self::Diff(old_diff), acp::ToolCallContent::Diff(new_diff)) => {
old_diff.read(cx).needs_update(
new_diff.old_text.as_deref().unwrap_or(""),
&new_diff.new_text,
@@ -635,10 +650,14 @@ impl ToolCallContent {
_ => true,
};
- if needs_update {
- *self = Self::from_acp(new, language_registry, path_style, terminals, cx)?;
+ if let Some(update) = Self::from_acp(new, language_registry, path_style, terminals, cx)? {
+ if needs_update {
+ *self = update;
+ }
+ Ok(true)
+ } else {
+ Ok(false)
}
- Ok(())
}
pub fn to_markdown(&self, cx: &App) -> String {
@@ -660,7 +679,7 @@ pub enum ToolCallUpdate {
impl ToolCallUpdate {
fn id(&self) -> &acp::ToolCallId {
match self {
- Self::UpdateFields(update) => &update.id,
+ Self::UpdateFields(update) => &update.tool_call_id,
Self::UpdateDiff(diff) => &diff.id,
Self::UpdateTerminal(terminal) => &terminal.id,
}
@@ -732,6 +751,7 @@ impl Plan {
acp::PlanEntryStatus::Completed => {
stats.completed += 1;
}
+ _ => {}
}
}
@@ -1154,6 +1174,7 @@ impl AcpThread {
current_mode_id,
..
}) => cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id)),
+ _ => {}
}
Ok(())
}
@@ -1287,11 +1308,7 @@ impl AcpThread {
label: cx.new(|cx| Markdown::new("Tool call not found".into(), None, None, cx)),
kind: acp::ToolKind::Fetch,
content: vec![ToolCallContent::ContentBlock(ContentBlock::new(
- acp::ContentBlock::Text(acp::TextContent {
- text: "Tool call not found".to_string(),
- annotations: None,
- meta: None,
- }),
+ "Tool call not found".into(),
&languages,
path_style,
cx,
@@ -1315,7 +1332,7 @@ impl AcpThread {
let location_updated = update.fields.locations.is_some();
call.update_fields(update.fields, languages, path_style, &self.terminals, cx)?;
if location_updated {
- self.resolve_locations(update.id, cx);
+ self.resolve_locations(update.tool_call_id, cx);
}
}
ToolCallUpdate::UpdateDiff(update) => {
@@ -1353,7 +1370,7 @@ impl AcpThread {
) -> Result<(), acp::Error> {
let language_registry = self.project.read(cx).languages().clone();
let path_style = self.project.read(cx).path_style(cx);
- let id = update.id.clone();
+ let id = update.tool_call_id.clone();
let agent = self.connection().telemetry_id();
let session = self.session_id();
@@ -1518,16 +1535,16 @@ impl AcpThread {
// some tools would (incorrectly) continue to auto-accept.
if let Some(allow_once_option) = options.iter().find_map(|option| {
if matches!(option.kind, acp::PermissionOptionKind::AllowOnce) {
- Some(option.id.clone())
+ Some(option.option_id.clone())
} else {
None
}
}) {
self.upsert_tool_call_inner(tool_call, ToolCallStatus::Pending, cx)?;
return Ok(async {
- acp::RequestPermissionOutcome::Selected {
- option_id: allow_once_option,
- }
+ acp::RequestPermissionOutcome::Selected(acp::SelectedPermissionOutcome::new(
+ allow_once_option,
+ ))
}
.boxed());
}
@@ -1543,7 +1560,9 @@ impl AcpThread {
let fut = async {
match rx.await {
- Ok(option) => acp::RequestPermissionOutcome::Selected { option_id: option },
+ Ok(option) => acp::RequestPermissionOutcome::Selected(
+ acp::SelectedPermissionOutcome::new(option),
+ ),
Err(oneshot::Canceled) => acp::RequestPermissionOutcome::Cancelled,
}
}
@@ -1570,6 +1589,7 @@ impl AcpThread {
acp::PermissionOptionKind::AllowOnce | acp::PermissionOptionKind::AllowAlways => {
ToolCallStatus::InProgress
}
+ _ => ToolCallStatus::InProgress,
};
let curr_status = mem::replace(&mut call.status, new_status);
@@ -1648,14 +1668,7 @@ impl AcpThread {
message: &str,
cx: &mut Context<Self>,
) -> BoxFuture<'static, Result<()>> {
- self.send(
- vec![acp::ContentBlock::Text(acp::TextContent {
- text: message.to_string(),
- annotations: None,
- meta: None,
- })],
- cx,
- )
+ self.send(vec![message.into()], cx)
}
pub fn send(
@@ -1669,11 +1682,7 @@ impl AcpThread {
self.project.read(cx).path_style(cx),
cx,
);
- let request = acp::PromptRequest {
- prompt: message.clone(),
- session_id: self.session_id.clone(),
- meta: None,
- };
+ let request = acp::PromptRequest::new(self.session_id.clone(), message.clone());
let git_store = self.project.read(cx).git_store().clone();
let message_id = if self.connection.truncate(&self.session_id, cx).is_some() {
@@ -1765,7 +1774,7 @@ impl AcpThread {
result,
Ok(Ok(acp::PromptResponse {
stop_reason: acp::StopReason::Cancelled,
- meta: None,
+ ..
}))
);
@@ -1781,7 +1790,7 @@ impl AcpThread {
// Handle refusal - distinguish between user prompt and tool call refusals
if let Ok(Ok(acp::PromptResponse {
stop_reason: acp::StopReason::Refusal,
- meta: _,
+ ..
})) = result
{
if let Some((user_msg_ix, _)) = this.last_user_message() {
@@ -2017,7 +2026,7 @@ impl AcpThread {
})?;
Ok(project.open_buffer(path, cx))
})
- .map_err(|e| acp::Error::internal_error().with_data(e.to_string()))
+ .map_err(|e| acp::Error::internal_error().data(e.to_string()))
.flatten()?;
let buffer = load.await?;
@@ -2050,7 +2059,7 @@ impl AcpThread {
let start_position = Point::new(line, 0);
if start_position > max_point {
- return Err(acp::Error::invalid_params().with_data(format!(
+ return Err(acp::Error::invalid_params().data(format!(
"Attempting to read beyond the end of the file, line {}:{}",
max_point.row + 1,
max_point.column
@@ -2202,7 +2211,7 @@ impl AcpThread {
let language_registry = project.read(cx).languages().clone();
let is_windows = project.read(cx).path_style(cx).is_windows();
- let terminal_id = acp::TerminalId(Uuid::new_v4().to_string().into());
+ let terminal_id = acp::TerminalId::new(Uuid::new_v4().to_string());
let terminal_task = cx.spawn({
let terminal_id = terminal_id.clone();
async move |_this, cx| {
@@ -2412,7 +2421,7 @@ mod tests {
.await
.unwrap();
- let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
+ let terminal_id = acp::TerminalId::new(uuid::Uuid::new_v4().to_string());
// Send Output BEFORE Created - should be buffered by acp_thread
thread.update(cx, |thread, cx| {
@@ -2474,7 +2483,7 @@ mod tests {
.await
.unwrap();
- let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
+ let terminal_id = acp::TerminalId::new(uuid::Uuid::new_v4().to_string());
// Send Output BEFORE Created
thread.update(cx, |thread, cx| {
@@ -2492,11 +2501,7 @@ mod tests {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id.clone(),
- status: acp::TerminalExitStatus {
- exit_code: Some(0),
- signal: None,
- meta: None,
- },
+ status: acp::TerminalExitStatus::new().exit_code(0),
},
cx,
);
@@ -2553,15 +2558,7 @@ mod tests {
// Test creating a new user message
thread.update(cx, |thread, cx| {
- thread.push_user_content_block(
- None,
- acp::ContentBlock::Text(acp::TextContent {
- annotations: None,
- text: "Hello, ".to_string(),
- meta: None,
- }),
- cx,
- );
+ thread.push_user_content_block(None, "Hello, ".into(), cx);
});
thread.update(cx, |thread, cx| {
@@ -2577,15 +2574,7 @@ mod tests {
// Test appending to existing user message
let message_1_id = UserMessageId::new();
thread.update(cx, |thread, cx| {
- thread.push_user_content_block(
- Some(message_1_id.clone()),
- acp::ContentBlock::Text(acp::TextContent {
- annotations: None,
- text: "world!".to_string(),
- meta: None,
- }),
- cx,
- );
+ thread.push_user_content_block(Some(message_1_id.clone()), "world!".into(), cx);
});
thread.update(cx, |thread, cx| {
@@ -2600,26 +2589,14 @@ mod tests {
// Test creating new user message after assistant message
thread.update(cx, |thread, cx| {
- thread.push_assistant_content_block(
- acp::ContentBlock::Text(acp::TextContent {
- annotations: None,
- text: "Assistant response".to_string(),
- meta: None,
- }),
- false,
- cx,
- );
+ thread.push_assistant_content_block("Assistant response".into(), false, cx);
});
let message_2_id = UserMessageId::new();
thread.update(cx, |thread, cx| {
thread.push_user_content_block(
Some(message_2_id.clone()),
- acp::ContentBlock::Text(acp::TextContent {
- annotations: None,
- text: "New user message".to_string(),
- meta: None,
- }),
+ "New user message".into(),
cx,
);
});
@@ -2647,27 +2624,22 @@ mod tests {
thread.update(&mut cx, |thread, cx| {
thread
.handle_session_update(
- acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
- content: "Thinking ".into(),
- meta: None,
- }),
+ acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk::new(
+ "Thinking ".into(),
+ )),
cx,
)
.unwrap();
thread
.handle_session_update(
- acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
- content: "hard!".into(),
- meta: None,
- }),
+ acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk::new(
+ "hard!".into(),
+ )),
cx,
)
.unwrap();
})?;
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
}
.boxed_local()
},
@@ -2735,10 +2707,7 @@ mod tests {
.unwrap()
.await
.unwrap();
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
}
.boxed_local()
},
@@ -2969,7 +2938,7 @@ mod tests {
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
- let id = acp::ToolCallId("test".into());
+ let id = acp::ToolCallId::new("test");
let connection = Rc::new(FakeAgentConnection::new().on_user_message({
let id = id.clone();
@@ -2979,26 +2948,17 @@ mod tests {
thread
.update(&mut cx, |thread, cx| {
thread.handle_session_update(
- acp::SessionUpdate::ToolCall(acp::ToolCall {
- id: id.clone(),
- title: "Label".into(),
- kind: acp::ToolKind::Fetch,
- status: acp::ToolCallStatus::InProgress,
- content: vec![],
- locations: vec![],
- raw_input: None,
- raw_output: None,
- meta: None,
- }),
+ acp::SessionUpdate::ToolCall(
+ acp::ToolCall::new(id.clone(), "Label")
+ .kind(acp::ToolKind::Fetch)
+ .status(acp::ToolCallStatus::InProgress),
+ ),
cx,
)
})
.unwrap()
.unwrap();
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
}
.boxed_local()
}
@@ -3040,14 +3000,10 @@ mod tests {
thread
.update(cx, |thread, cx| {
thread.handle_session_update(
- acp::SessionUpdate::ToolCallUpdate(acp::ToolCallUpdate {
+ acp::SessionUpdate::ToolCallUpdate(acp::ToolCallUpdate::new(
id,
- fields: acp::ToolCallUpdateFields {
- status: Some(acp::ToolCallStatus::Completed),
- ..Default::default()
- },
- meta: None,
- }),
+ acp::ToolCallUpdateFields::new().status(acp::ToolCallStatus::Completed),
+ )),
cx,
)
})
@@ -3079,33 +3035,21 @@ mod tests {
thread
.update(&mut cx, |thread, cx| {
thread.handle_session_update(
- acp::SessionUpdate::ToolCall(acp::ToolCall {
- id: acp::ToolCallId("test".into()),
- title: "Label".into(),
- kind: acp::ToolKind::Edit,
- status: acp::ToolCallStatus::Completed,
- content: vec![acp::ToolCallContent::Diff {
- diff: acp::Diff {
- path: "/test/test.txt".into(),
- old_text: None,
- new_text: "foo".into(),
- meta: None,
- },
- }],
- locations: vec![],
- raw_input: None,
- raw_output: None,
- meta: None,
- }),
+ acp::SessionUpdate::ToolCall(
+ acp::ToolCall::new("test", "Label")
+ .kind(acp::ToolKind::Edit)
+ .status(acp::ToolCallStatus::Completed)
+ .content(vec![acp::ToolCallContent::Diff(acp::Diff::new(
+ "/test/test.txt",
+ "foo",
+ ))]),
+ ),
cx,
)
})
.unwrap()
.unwrap();
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
}
.boxed_local()
}
@@ -3158,18 +3102,14 @@ mod tests {
thread.update(&mut cx, |thread, cx| {
thread
.handle_session_update(
- acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
- content: content.text.to_uppercase().into(),
- meta: None,
- }),
+ acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new(
+ content.text.to_uppercase().into(),
+ )),
cx,
)
.unwrap();
})?;
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
}
.boxed_local()
}
@@ -3325,34 +3265,22 @@ mod tests {
thread.update(&mut cx, |thread, cx| {
thread
.handle_session_update(
- acp::SessionUpdate::ToolCall(acp::ToolCall {
- id: acp::ToolCallId("tool1".into()),
- title: "Test Tool".into(),
- kind: acp::ToolKind::Fetch,
- status: acp::ToolCallStatus::Completed,
- content: vec![],
- locations: vec![],
- raw_input: Some(serde_json::json!({"query": "test"})),
- raw_output: Some(
- serde_json::json!({"result": "inappropriate content"}),
- ),
- meta: None,
- }),
+ acp::SessionUpdate::ToolCall(
+ acp::ToolCall::new("tool1", "Test Tool")
+ .kind(acp::ToolKind::Fetch)
+ .status(acp::ToolCallStatus::Completed)
+ .raw_input(serde_json::json!({"query": "test"}))
+ .raw_output(serde_json::json!({"result": "inappropriate content"})),
+ ),
cx,
)
.unwrap();
})?;
// Now return refusal because of the tool result
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::Refusal,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::Refusal))
} else {
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
}
}
.boxed_local()
@@ -3380,16 +3308,7 @@ mod tests {
});
// Send a user message - this will trigger tool call and then refusal
- let send_task = thread.update(cx, |thread, cx| {
- thread.send(
- vec![acp::ContentBlock::Text(acp::TextContent {
- text: "Hello".into(),
- annotations: None,
- meta: None,
- })],
- cx,
- )
- });
+ let send_task = thread.update(cx, |thread, cx| thread.send(vec!["Hello".into()], cx));
cx.background_executor.spawn(send_task).detach();
cx.run_until_parked();
@@ -3435,21 +3354,11 @@ mod tests {
let refuse_next = refuse_next.clone();
move |_request, _thread, _cx| {
if refuse_next.load(SeqCst) {
- async move {
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::Refusal,
- meta: None,
- })
- }
- .boxed_local()
+ async move { Ok(acp::PromptResponse::new(acp::StopReason::Refusal)) }
+ .boxed_local()
} else {
- async move {
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
- }
- .boxed_local()
+ async move { Ok(acp::PromptResponse::new(acp::StopReason::EndTurn)) }
+ .boxed_local()
}
}
}));
@@ -3506,10 +3415,7 @@ mod tests {
let refuse_next = refuse_next.clone();
async move {
if refuse_next.load(SeqCst) {
- return Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::Refusal,
- meta: None,
- });
+ return Ok(acp::PromptResponse::new(acp::StopReason::Refusal));
}
let acp::ContentBlock::Text(content) = &request.prompt[0] else {
@@ -3518,18 +3424,14 @@ mod tests {
thread.update(&mut cx, |thread, cx| {
thread
.handle_session_update(
- acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
- content: content.text.to_uppercase().into(),
- meta: None,
- }),
+ acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new(
+ content.text.to_uppercase().into(),
+ )),
cx,
)
.unwrap();
})?;
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
}
.boxed_local()
}
@@ -3668,13 +3570,12 @@ mod tests {
_cwd: &Path,
cx: &mut App,
) -> Task<gpui::Result<Entity<AcpThread>>> {
- let session_id = acp::SessionId(
+ let session_id = acp::SessionId::new(
rand::rng()
.sample_iter(&distr::Alphanumeric)
.take(7)
.map(char::from)
- .collect::<String>()
- .into(),
+ .collect::<String>(),
);
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let thread = cx.new(|cx| {
@@ -3684,12 +3585,12 @@ mod tests {
project,
action_log,
session_id.clone(),
- watch::Receiver::constant(acp::PromptCapabilities {
- image: true,
- audio: true,
- embedded_context: true,
- meta: None,
- }),
+ watch::Receiver::constant(
+ acp::PromptCapabilities::new()
+ .image(true)
+ .audio(true)
+ .embedded_context(true),
+ ),
cx,
)
});
@@ -3718,10 +3619,7 @@ mod tests {
let thread = thread.clone();
cx.spawn(async move |cx| handler(params, thread, cx.clone()).await)
} else {
- Task::ready(Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- }))
+ Task::ready(Ok(acp::PromptResponse::new(acp::StopReason::EndTurn)))
}
}
@@ -3776,17 +3674,13 @@ mod tests {
.unwrap();
// Try to update a tool call that doesn't exist
- let nonexistent_id = acp::ToolCallId("nonexistent-tool-call".into());
+ let nonexistent_id = acp::ToolCallId::new("nonexistent-tool-call");
thread.update(cx, |thread, cx| {
let result = thread.handle_session_update(
- acp::SessionUpdate::ToolCallUpdate(acp::ToolCallUpdate {
- id: nonexistent_id.clone(),
- fields: acp::ToolCallUpdateFields {
- status: Some(acp::ToolCallStatus::Completed),
- ..Default::default()
- },
- meta: None,
- }),
+ acp::SessionUpdate::ToolCallUpdate(acp::ToolCallUpdate::new(
+ nonexistent_id.clone(),
+ acp::ToolCallUpdateFields::new().status(acp::ToolCallStatus::Completed),
+ )),
cx,
);
@@ -3861,7 +3755,7 @@ mod tests {
.unwrap();
// Create 2 terminals BEFORE the checkpoint that have completed running
- let terminal_id_1 = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
+ let terminal_id_1 = acp::TerminalId::new(uuid::Uuid::new_v4().to_string());
let mock_terminal_1 = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
@@ -3900,17 +3794,13 @@ mod tests {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id_1.clone(),
- status: acp::TerminalExitStatus {
- exit_code: Some(0),
- signal: None,
- meta: None,
- },
+ status: acp::TerminalExitStatus::new().exit_code(0),
},
cx,
);
});
- let terminal_id_2 = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
+ let terminal_id_2 = acp::TerminalId::new(uuid::Uuid::new_v4().to_string());
let mock_terminal_2 = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
@@ -3949,11 +3839,7 @@ mod tests {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id_2.clone(),
- status: acp::TerminalExitStatus {
- exit_code: Some(0),
- signal: None,
- meta: None,
- },
+ status: acp::TerminalExitStatus::new().exit_code(0),
},
cx,
);
@@ -3973,7 +3859,7 @@ mod tests {
// Create a terminal AFTER the checkpoint we'll restore to.
// This simulates the AI agent starting a long-running terminal command.
- let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
+ let terminal_id = acp::TerminalId::new(uuid::Uuid::new_v4().to_string());
let mock_terminal = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
@@ -4015,21 +3901,15 @@ mod tests {
thread.update(cx, |thread, cx| {
thread
.handle_session_update(
- acp::SessionUpdate::ToolCall(acp::ToolCall {
- id: acp::ToolCallId("terminal-tool-1".into()),
- title: "Running command".into(),
- kind: acp::ToolKind::Execute,
- status: acp::ToolCallStatus::InProgress,
- content: vec![acp::ToolCallContent::Terminal {
- terminal_id: terminal_id.clone(),
- }],
- locations: vec![],
- raw_input: Some(
- serde_json::json!({"command": "sleep 1000", "cd": "/test"}),
- ),
- raw_output: None,
- meta: None,
- }),
+ acp::SessionUpdate::ToolCall(
+ acp::ToolCall::new("terminal-tool-1", "Running command")
+ .kind(acp::ToolKind::Execute)
+ .status(acp::ToolCallStatus::InProgress)
+ .content(vec![acp::ToolCallContent::Terminal(acp::Terminal::new(
+ terminal_id.clone(),
+ ))])
+ .raw_input(serde_json::json!({"command": "sleep 1000", "cd": "/test"})),
+ ),
cx,
)
.unwrap();
@@ -336,7 +336,7 @@ mod test_support {
_cwd: &Path,
cx: &mut gpui::App,
) -> Task<gpui::Result<Entity<AcpThread>>> {
- let session_id = acp::SessionId(self.sessions.lock().len().to_string().into());
+ let session_id = acp::SessionId::new(self.sessions.lock().len().to_string());
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let thread = cx.new(|cx| {
AcpThread::new(
@@ -345,12 +345,12 @@ mod test_support {
project,
action_log,
session_id.clone(),
- watch::Receiver::constant(acp::PromptCapabilities {
- image: true,
- audio: true,
- embedded_context: true,
- meta: None,
- }),
+ watch::Receiver::constant(
+ acp::PromptCapabilities::new()
+ .image(true)
+ .audio(true)
+ .embedded_context(true),
+ ),
cx,
)
});
@@ -389,10 +389,7 @@ mod test_support {
response_tx.replace(tx);
cx.spawn(async move |_| {
let stop_reason = rx.await?;
- Ok(acp::PromptResponse {
- stop_reason,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(stop_reason))
})
} else {
for update in self.next_prompt_updates.lock().drain(..) {
@@ -400,7 +397,7 @@ mod test_support {
let update = update.clone();
let permission_request = if let acp::SessionUpdate::ToolCall(tool_call) =
&update
- && let Some(options) = self.permission_requests.get(&tool_call.id)
+ && let Some(options) = self.permission_requests.get(&tool_call.tool_call_id)
{
Some((tool_call.clone(), options.clone()))
} else {
@@ -429,10 +426,7 @@ mod test_support {
cx.spawn(async move |_| {
try_join_all(tasks).await?;
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
})
}
}
@@ -108,7 +108,7 @@ impl MentionUri {
if let Some(thread_id) = path.strip_prefix("/agent/thread/") {
let name = single_query_param(&url, "name")?.context("Missing thread name")?;
Ok(Self::Thread {
- id: acp::SessionId(thread_id.into()),
+ id: acp::SessionId::new(thread_id),
name,
})
} else if let Some(path) = path.strip_prefix("/agent/text-thread/") {
@@ -75,11 +75,15 @@ impl Terminal {
let exit_status = exit_status.map(portable_pty::ExitStatus::from);
- acp::TerminalExitStatus {
- exit_code: exit_status.as_ref().map(|e| e.exit_code()),
- signal: exit_status.and_then(|e| e.signal().map(Into::into)),
- meta: None,
+ let mut status = acp::TerminalExitStatus::new();
+
+ if let Some(exit_status) = exit_status.as_ref() {
+ status = status.exit_code(exit_status.exit_code());
+ if let Some(signal) = exit_status.signal() {
+ status = status.signal(signal);
+ }
}
+ status
})
.shared(),
}
@@ -101,27 +105,23 @@ impl Terminal {
pub fn current_output(&self, cx: &App) -> acp::TerminalOutputResponse {
if let Some(output) = self.output.as_ref() {
- let exit_status = output.exit_status.map(portable_pty::ExitStatus::from);
-
- acp::TerminalOutputResponse {
- output: output.content.clone(),
- truncated: output.original_content_len > output.content.len(),
- exit_status: Some(acp::TerminalExitStatus {
- exit_code: exit_status.as_ref().map(|e| e.exit_code()),
- signal: exit_status.and_then(|e| e.signal().map(Into::into)),
- meta: None,
- }),
- meta: None,
+ let mut exit_status = acp::TerminalExitStatus::new();
+ if let Some(status) = output.exit_status.map(portable_pty::ExitStatus::from) {
+ exit_status = exit_status.exit_code(status.exit_code());
+ if let Some(signal) = status.signal() {
+ exit_status = exit_status.signal(signal);
+ }
}
+
+ acp::TerminalOutputResponse::new(
+ output.content.clone(),
+ output.original_content_len > output.content.len(),
+ )
+ .exit_status(exit_status)
} else {
let (current_content, original_len) = self.truncated_output(cx);
-
- acp::TerminalOutputResponse {
- truncated: current_content.len() < original_len,
- output: current_content,
- exit_status: None,
- meta: None,
- }
+ let truncated = current_content.len() < original_len;
+ acp::TerminalOutputResponse::new(current_content, truncated)
}
}
@@ -170,7 +170,7 @@ impl LanguageModels {
}
fn model_id(model: &Arc<dyn LanguageModel>) -> acp::ModelId {
- acp::ModelId(format!("{}/{}", model.provider_id().0, model.id().0).into())
+ acp::ModelId::new(format!("{}/{}", model.provider_id().0, model.id().0))
}
fn authenticate_all_language_model_providers(cx: &mut App) -> Task<()> {
@@ -789,28 +789,12 @@ impl NativeAgentConnection {
}
ThreadEvent::AgentText(text) => {
acp_thread.update(cx, |thread, cx| {
- thread.push_assistant_content_block(
- acp::ContentBlock::Text(acp::TextContent {
- text,
- annotations: None,
- meta: None,
- }),
- false,
- cx,
- )
+ thread.push_assistant_content_block(text.into(), false, cx)
})?;
}
ThreadEvent::AgentThinking(text) => {
acp_thread.update(cx, |thread, cx| {
- thread.push_assistant_content_block(
- acp::ContentBlock::Text(acp::TextContent {
- text,
- annotations: None,
- meta: None,
- }),
- true,
- cx,
- )
+ thread.push_assistant_content_block(text.into(), true, cx)
})?;
}
ThreadEvent::ToolCallAuthorization(ToolCallAuthorization {
@@ -824,8 +808,9 @@ impl NativeAgentConnection {
)
})??;
cx.background_spawn(async move {
- if let acp::RequestPermissionOutcome::Selected { option_id } =
- outcome_task.await
+ if let acp::RequestPermissionOutcome::Selected(
+ acp::SelectedPermissionOutcome { option_id, .. },
+ ) = outcome_task.await
{
response
.send(option_id)
@@ -852,10 +837,7 @@ impl NativeAgentConnection {
}
ThreadEvent::Stop(stop_reason) => {
log::debug!("Assistant message complete: {:?}", stop_reason);
- return Ok(acp::PromptResponse {
- stop_reason,
- meta: None,
- });
+ return Ok(acp::PromptResponse::new(stop_reason));
}
}
}
@@ -867,10 +849,7 @@ impl NativeAgentConnection {
}
log::debug!("Response stream completed");
- anyhow::Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::EndTurn,
- meta: None,
- })
+ anyhow::Ok(acp::PromptResponse::new(acp::StopReason::EndTurn))
})
}
}
@@ -1374,7 +1353,7 @@ mod internal_tests {
IndexMap::from_iter([(
AgentModelGroupName("Fake".into()),
vec![AgentModelInfo {
- id: acp::ModelId("fake/fake".into()),
+ id: acp::ModelId::new("fake/fake"),
name: "Fake".into(),
description: None,
icon: Some(ui::IconName::ZedAssistant),
@@ -1435,7 +1414,7 @@ mod internal_tests {
// Select a model
let selector = connection.model_selector(&session_id).unwrap();
- let model_id = acp::ModelId("fake/fake".into());
+ let model_id = acp::ModelId::new("fake/fake");
cx.update(|cx| selector.select_model(model_id.clone(), cx))
.await
.unwrap();
@@ -1521,20 +1500,14 @@ mod internal_tests {
thread.send(
vec![
"What does ".into(),
- acp::ContentBlock::ResourceLink(acp::ResourceLink {
- name: "b.md".into(),
- uri: MentionUri::File {
+ acp::ContentBlock::ResourceLink(acp::ResourceLink::new(
+ "b.md",
+ MentionUri::File {
abs_path: path!("/a/b.md").into(),
}
.to_uri()
.to_string(),
- annotations: None,
- description: None,
- mime_type: None,
- size: None,
- title: None,
- meta: None,
- }),
+ )),
" mean?".into(),
],
cx,
@@ -366,7 +366,7 @@ impl ThreadsDatabase {
for (id, summary, updated_at) in rows {
threads.push(DbThreadMetadata {
- id: acp::SessionId(id),
+ id: acp::SessionId::new(id),
title: summary.into(),
updated_at: DateTime::parse_from_rfc3339(&updated_at)?.with_timezone(&Utc),
});
@@ -354,9 +354,9 @@ impl HistoryStore {
.into_iter()
.take(MAX_RECENTLY_OPENED_ENTRIES)
.flat_map(|entry| match entry {
- SerializedRecentOpen::AcpThread(id) => Some(HistoryEntryId::AcpThread(
- acp::SessionId(id.as_str().into()),
- )),
+ SerializedRecentOpen::AcpThread(id) => {
+ Some(HistoryEntryId::AcpThread(acp::SessionId::new(id.as_str())))
+ }
SerializedRecentOpen::TextThread(file_name) => Some(
HistoryEntryId::TextThread(text_threads_dir().join(file_name).into()),
),
@@ -493,14 +493,14 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
// Approve the first
tool_call_auth_1
.response
- .send(tool_call_auth_1.options[1].id.clone())
+ .send(tool_call_auth_1.options[1].option_id.clone())
.unwrap();
cx.run_until_parked();
// Reject the second
tool_call_auth_2
.response
- .send(tool_call_auth_1.options[2].id.clone())
+ .send(tool_call_auth_1.options[2].option_id.clone())
.unwrap();
cx.run_until_parked();
@@ -510,14 +510,14 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
message.content,
vec![
language_model::MessageContent::ToolResult(LanguageModelToolResult {
- tool_use_id: tool_call_auth_1.tool_call.id.0.to_string().into(),
+ tool_use_id: tool_call_auth_1.tool_call.tool_call_id.0.to_string().into(),
tool_name: ToolRequiringPermission::name().into(),
is_error: false,
content: "Allowed".into(),
output: Some("Allowed".into())
}),
language_model::MessageContent::ToolResult(LanguageModelToolResult {
- tool_use_id: tool_call_auth_2.tool_call.id.0.to_string().into(),
+ tool_use_id: tool_call_auth_2.tool_call.tool_call_id.0.to_string().into(),
tool_name: ToolRequiringPermission::name().into(),
is_error: true,
content: "Permission to run tool denied by user".into(),
@@ -543,7 +543,7 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
let tool_call_auth_3 = next_tool_call_authorization(&mut events).await;
tool_call_auth_3
.response
- .send(tool_call_auth_3.options[0].id.clone())
+ .send(tool_call_auth_3.options[0].option_id.clone())
.unwrap();
cx.run_until_parked();
let completion = fake_model.pending_completions().pop().unwrap();
@@ -552,7 +552,7 @@ async fn test_tool_authorization(cx: &mut TestAppContext) {
message.content,
vec![language_model::MessageContent::ToolResult(
LanguageModelToolResult {
- tool_use_id: tool_call_auth_3.tool_call.id.0.to_string().into(),
+ tool_use_id: tool_call_auth_3.tool_call.tool_call_id.0.to_string().into(),
tool_name: ToolRequiringPermission::name().into(),
is_error: false,
content: "Allowed".into(),
@@ -1353,20 +1353,20 @@ async fn test_cancellation(cx: &mut TestAppContext) {
ThreadEvent::ToolCall(tool_call) => {
assert_eq!(tool_call.title, expected_tools.remove(0));
if tool_call.title == "Echo" {
- echo_id = Some(tool_call.id);
+ echo_id = Some(tool_call.tool_call_id);
}
}
ThreadEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateFields(
acp::ToolCallUpdate {
- id,
+ tool_call_id,
fields:
acp::ToolCallUpdateFields {
status: Some(acp::ToolCallStatus::Completed),
..
},
- meta: None,
+ ..
},
- )) if Some(&id) == echo_id.as_ref() => {
+ )) if Some(&tool_call_id) == echo_id.as_ref() => {
echo_completed = true;
}
_ => {}
@@ -1995,11 +1995,7 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
.update(|cx| {
connection.prompt(
Some(acp_thread::UserMessageId::new()),
- acp::PromptRequest {
- session_id: session_id.clone(),
- prompt: vec!["ghi".into()],
- meta: None,
- },
+ acp::PromptRequest::new(session_id.clone(), vec!["ghi".into()]),
cx,
)
})
@@ -2056,68 +2052,50 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
let tool_call = expect_tool_call(&mut events).await;
assert_eq!(
tool_call,
- acp::ToolCall {
- id: acp::ToolCallId("1".into()),
- title: "Thinking".into(),
- kind: acp::ToolKind::Think,
- status: acp::ToolCallStatus::Pending,
- content: vec![],
- locations: vec![],
- raw_input: Some(json!({})),
- raw_output: None,
- meta: Some(json!({ "tool_name": "thinking" })),
- }
+ acp::ToolCall::new("1", "Thinking")
+ .kind(acp::ToolKind::Think)
+ .raw_input(json!({}))
+ .meta(acp::Meta::from_iter([(
+ "tool_name".into(),
+ "thinking".into()
+ )]))
);
let update = expect_tool_call_update_fields(&mut events).await;
assert_eq!(
update,
- acp::ToolCallUpdate {
- id: acp::ToolCallId("1".into()),
- fields: acp::ToolCallUpdateFields {
- title: Some("Thinking".into()),
- kind: Some(acp::ToolKind::Think),
- raw_input: Some(json!({ "content": "Thinking hard!" })),
- ..Default::default()
- },
- meta: None,
- }
+ acp::ToolCallUpdate::new(
+ "1",
+ acp::ToolCallUpdateFields::new()
+ .title("Thinking")
+ .kind(acp::ToolKind::Think)
+ .raw_input(json!({ "content": "Thinking hard!"}))
+ )
);
let update = expect_tool_call_update_fields(&mut events).await;
assert_eq!(
update,
- acp::ToolCallUpdate {
- id: acp::ToolCallId("1".into()),
- fields: acp::ToolCallUpdateFields {
- status: Some(acp::ToolCallStatus::InProgress),
- ..Default::default()
- },
- meta: None,
- }
+ acp::ToolCallUpdate::new(
+ "1",
+ acp::ToolCallUpdateFields::new().status(acp::ToolCallStatus::InProgress)
+ )
);
let update = expect_tool_call_update_fields(&mut events).await;
assert_eq!(
update,
- acp::ToolCallUpdate {
- id: acp::ToolCallId("1".into()),
- fields: acp::ToolCallUpdateFields {
- content: Some(vec!["Thinking hard!".into()]),
- ..Default::default()
- },
- meta: None,
- }
+ acp::ToolCallUpdate::new(
+ "1",
+ acp::ToolCallUpdateFields::new().content(vec!["Thinking hard!".into()])
+ )
);
let update = expect_tool_call_update_fields(&mut events).await;
assert_eq!(
update,
- acp::ToolCallUpdate {
- id: acp::ToolCallId("1".into()),
- fields: acp::ToolCallUpdateFields {
- status: Some(acp::ToolCallStatus::Completed),
- raw_output: Some("Finished thinking.".into()),
- ..Default::default()
- },
- meta: None,
- }
+ acp::ToolCallUpdate::new(
+ "1",
+ acp::ToolCallUpdateFields::new()
+ .status(acp::ToolCallStatus::Completed)
+ .raw_output("Finished thinking.".into())
+ )
);
}
@@ -619,12 +619,9 @@ pub struct Thread {
impl Thread {
fn prompt_capabilities(model: Option<&dyn LanguageModel>) -> acp::PromptCapabilities {
let image = model.map_or(true, |model| model.supports_images());
- acp::PromptCapabilities {
- meta: None,
- image,
- audio: false,
- embedded_context: true,
- }
+ acp::PromptCapabilities::new()
+ .image(image)
+ .embedded_context(true)
}
pub fn new(
@@ -640,7 +637,7 @@ impl Thread {
let (prompt_capabilities_tx, prompt_capabilities_rx) =
watch::channel(Self::prompt_capabilities(model.as_deref()));
Self {
- id: acp::SessionId(uuid::Uuid::new_v4().to_string().into()),
+ id: acp::SessionId::new(uuid::Uuid::new_v4().to_string()),
prompt_id: PromptId::new(),
updated_at: Utc::now(),
title: None,
@@ -737,17 +734,11 @@ impl Thread {
let Some(tool) = tool else {
stream
.0
- .unbounded_send(Ok(ThreadEvent::ToolCall(acp::ToolCall {
- meta: None,
- id: acp::ToolCallId(tool_use.id.to_string().into()),
- title: tool_use.name.to_string(),
- kind: acp::ToolKind::Other,
- status: acp::ToolCallStatus::Failed,
- content: Vec::new(),
- locations: Vec::new(),
- raw_input: Some(tool_use.input.clone()),
- raw_output: None,
- })))
+ .unbounded_send(Ok(ThreadEvent::ToolCall(
+ acp::ToolCall::new(tool_use.id.to_string(), tool_use.name.to_string())
+ .status(acp::ToolCallStatus::Failed)
+ .raw_input(tool_use.input.clone()),
+ )))
.ok();
return;
};
@@ -775,24 +766,20 @@ impl Thread {
.log_err();
}
- stream.update_tool_call_fields(
- &tool_use.id,
- acp::ToolCallUpdateFields {
- status: Some(
- tool_result
- .as_ref()
- .map_or(acp::ToolCallStatus::Failed, |result| {
- if result.is_error {
- acp::ToolCallStatus::Failed
- } else {
- acp::ToolCallStatus::Completed
- }
- }),
- ),
- raw_output: output,
- ..Default::default()
+ let mut fields = acp::ToolCallUpdateFields::new().status(tool_result.as_ref().map_or(
+ acp::ToolCallStatus::Failed,
+ |result| {
+ if result.is_error {
+ acp::ToolCallStatus::Failed
+ } else {
+ acp::ToolCallStatus::Completed
+ }
},
- );
+ ));
+ if let Some(output) = output {
+ fields = fields.raw_output(output);
+ }
+ stream.update_tool_call_fields(&tool_use.id, fields);
}
pub fn from_db(
@@ -1272,18 +1259,15 @@ impl Thread {
while let Some(tool_result) = tool_results.next().await {
log::debug!("Tool finished {:?}", tool_result);
- event_stream.update_tool_call_fields(
- &tool_result.tool_use_id,
- acp::ToolCallUpdateFields {
- status: Some(if tool_result.is_error {
- acp::ToolCallStatus::Failed
- } else {
- acp::ToolCallStatus::Completed
- }),
- raw_output: tool_result.output.clone(),
- ..Default::default()
- },
- );
+ let mut fields = acp::ToolCallUpdateFields::new().status(if tool_result.is_error {
+ acp::ToolCallStatus::Failed
+ } else {
+ acp::ToolCallStatus::Completed
+ });
+ if let Some(output) = &tool_result.output {
+ fields = fields.raw_output(output.clone());
+ }
+ event_stream.update_tool_call_fields(&tool_result.tool_use_id, fields);
this.update(cx, |this, _cx| {
this.pending_message()
.tool_results
@@ -1560,12 +1544,10 @@ impl Thread {
} else {
event_stream.update_tool_call_fields(
&tool_use.id,
- acp::ToolCallUpdateFields {
- title: Some(title.into()),
- kind: Some(kind),
- raw_input: Some(tool_use.input.clone()),
- ..Default::default()
- },
+ acp::ToolCallUpdateFields::new()
+ .title(title)
+ .kind(kind)
+ .raw_input(tool_use.input.clone()),
);
}
@@ -1587,10 +1569,9 @@ impl Thread {
let fs = self.project.read(cx).fs().clone();
let tool_event_stream =
ToolCallEventStream::new(tool_use.id.clone(), event_stream.clone(), Some(fs));
- tool_event_stream.update_fields(acp::ToolCallUpdateFields {
- status: Some(acp::ToolCallStatus::InProgress),
- ..Default::default()
- });
+ tool_event_stream.update_fields(
+ acp::ToolCallUpdateFields::new().status(acp::ToolCallStatus::InProgress),
+ );
let supports_images = self.model().is_some_and(|model| model.supports_images());
let tool_result = tool.run(tool_use.input, tool_event_stream, cx);
log::debug!("Running tool {}", tool_use.name);
@@ -2381,19 +2362,13 @@ impl ThreadEventStream {
kind: acp::ToolKind,
input: serde_json::Value,
) -> acp::ToolCall {
- acp::ToolCall {
- meta: Some(serde_json::json!({
- "tool_name": tool_name
- })),
- id: acp::ToolCallId(id.to_string().into()),
- title,
- kind,
- status: acp::ToolCallStatus::Pending,
- content: vec![],
- locations: vec![],
- raw_input: Some(input),
- raw_output: None,
- }
+ acp::ToolCall::new(id.to_string(), title)
+ .kind(kind)
+ .raw_input(input)
+ .meta(acp::Meta::from_iter([(
+ "tool_name".into(),
+ tool_name.into(),
+ )]))
}
fn update_tool_call_fields(
@@ -2403,12 +2378,7 @@ impl ThreadEventStream {
) {
self.0
.unbounded_send(Ok(ThreadEvent::ToolCallUpdate(
- acp::ToolCallUpdate {
- meta: None,
- id: acp::ToolCallId(tool_use_id.to_string().into()),
- fields,
- }
- .into(),
+ acp::ToolCallUpdate::new(tool_use_id.to_string(), fields).into(),
)))
.ok();
}
@@ -2471,7 +2441,7 @@ impl ToolCallEventStream {
.0
.unbounded_send(Ok(ThreadEvent::ToolCallUpdate(
acp_thread::ToolCallUpdateDiff {
- id: acp::ToolCallId(self.tool_use_id.to_string().into()),
+ id: acp::ToolCallId::new(self.tool_use_id.to_string()),
diff,
}
.into(),
@@ -2489,33 +2459,26 @@ impl ToolCallEventStream {
.0
.unbounded_send(Ok(ThreadEvent::ToolCallAuthorization(
ToolCallAuthorization {
- tool_call: acp::ToolCallUpdate {
- meta: None,
- id: acp::ToolCallId(self.tool_use_id.to_string().into()),
- fields: acp::ToolCallUpdateFields {
- title: Some(title.into()),
- ..Default::default()
- },
- },
+ tool_call: acp::ToolCallUpdate::new(
+ self.tool_use_id.to_string(),
+ acp::ToolCallUpdateFields::new().title(title),
+ ),
options: vec![
- acp::PermissionOption {
- id: acp::PermissionOptionId("always_allow".into()),
- name: "Always Allow".into(),
- kind: acp::PermissionOptionKind::AllowAlways,
- meta: None,
- },
- acp::PermissionOption {
- id: acp::PermissionOptionId("allow".into()),
- name: "Allow".into(),
- kind: acp::PermissionOptionKind::AllowOnce,
- meta: None,
- },
- acp::PermissionOption {
- id: acp::PermissionOptionId("deny".into()),
- name: "Deny".into(),
- kind: acp::PermissionOptionKind::RejectOnce,
- meta: None,
- },
+ acp::PermissionOption::new(
+ acp::PermissionOptionId::new("always_allow"),
+ "Always Allow",
+ acp::PermissionOptionKind::AllowAlways,
+ ),
+ acp::PermissionOption::new(
+ acp::PermissionOptionId::new("allow"),
+ "Allow",
+ acp::PermissionOptionKind::AllowOnce,
+ ),
+ acp::PermissionOption::new(
+ acp::PermissionOptionId::new("deny"),
+ "Deny",
+ acp::PermissionOptionKind::RejectOnce,
+ ),
],
response: response_tx,
},
@@ -2660,7 +2623,15 @@ impl UserMessageContent {
// TODO
Self::Text("[blob]".to_string())
}
+ other => {
+ log::warn!("Unexpected content type: {:?}", other);
+ Self::Text("[unknown]".to_string())
+ }
},
+ other => {
+ log::warn!("Unexpected content type: {:?}", other);
+ Self::Text("[unknown]".to_string())
+ }
}
}
}
@@ -2668,32 +2639,15 @@ impl UserMessageContent {
impl From<UserMessageContent> for acp::ContentBlock {
fn from(content: UserMessageContent) -> Self {
match content {
- UserMessageContent::Text(text) => acp::ContentBlock::Text(acp::TextContent {
- text,
- annotations: None,
- meta: None,
- }),
- UserMessageContent::Image(image) => acp::ContentBlock::Image(acp::ImageContent {
- data: image.source.to_string(),
- mime_type: "image/png".to_string(),
- meta: None,
- annotations: None,
- uri: None,
- }),
- UserMessageContent::Mention { uri, content } => {
- acp::ContentBlock::Resource(acp::EmbeddedResource {
- meta: None,
- resource: acp::EmbeddedResourceResource::TextResourceContents(
- acp::TextResourceContents {
- meta: None,
- mime_type: None,
- text: content,
- uri: uri.to_uri().to_string(),
- },
- ),
- annotations: None,
- })
+ UserMessageContent::Text(text) => text.into(),
+ UserMessageContent::Image(image) => {
+ acp::ContentBlock::Image(acp::ImageContent::new(image.source, "image/png"))
}
+ UserMessageContent::Mention { uri, content } => acp::ContentBlock::Resource(
+ acp::EmbeddedResource::new(acp::EmbeddedResourceResource::TextResourceContents(
+ acp::TextResourceContents::new(content, uri.to_uri().to_string()),
+ )),
+ ),
}
}
}
@@ -273,14 +273,9 @@ impl AgentTool for EditFileTool {
};
let abs_path = project.read(cx).absolute_path(&project_path, cx);
if let Some(abs_path) = abs_path.clone() {
- event_stream.update_fields(ToolCallUpdateFields {
- locations: Some(vec![acp::ToolCallLocation {
- path: abs_path,
- line: None,
- meta: None,
- }]),
- ..Default::default()
- });
+ event_stream.update_fields(
+ ToolCallUpdateFields::new().locations(vec![acp::ToolCallLocation::new(abs_path)]),
+ );
}
let authorize = self.authorize(&input, &event_stream, cx);
@@ -389,10 +384,11 @@ impl AgentTool for EditFileTool {
range.start.to_point(&buffer.snapshot()).row
}).ok();
if let Some(abs_path) = abs_path.clone() {
- event_stream.update_fields(ToolCallUpdateFields {
- locations: Some(vec![ToolCallLocation { path: abs_path, line, meta: None }]),
- ..Default::default()
- });
+ let mut location = ToolCallLocation::new(abs_path);
+ if let Some(line) = line {
+ location = location.line(line);
+ }
+ event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![location]));
}
emitted_location = true;
}
@@ -118,33 +118,29 @@ impl AgentTool for FindPathTool {
let paginated_matches: &[PathBuf] = &matches[cmp::min(input.offset, matches.len())
..cmp::min(input.offset + RESULTS_PER_PAGE, matches.len())];
- event_stream.update_fields(acp::ToolCallUpdateFields {
- title: Some(if paginated_matches.is_empty() {
- "No matches".into()
- } else if paginated_matches.len() == 1 {
- "1 match".into()
- } else {
- format!("{} matches", paginated_matches.len())
- }),
- content: Some(
- paginated_matches
- .iter()
- .map(|path| acp::ToolCallContent::Content {
- content: acp::ContentBlock::ResourceLink(acp::ResourceLink {
- uri: format!("file://{}", path.display()),
- name: path.to_string_lossy().into(),
- annotations: None,
- description: None,
- mime_type: None,
- size: None,
- title: None,
- meta: None,
- }),
- })
- .collect(),
- ),
- ..Default::default()
- });
+ event_stream.update_fields(
+ acp::ToolCallUpdateFields::new()
+ .title(if paginated_matches.is_empty() {
+ "No matches".into()
+ } else if paginated_matches.len() == 1 {
+ "1 match".into()
+ } else {
+ format!("{} matches", paginated_matches.len())
+ })
+ .content(
+ paginated_matches
+ .iter()
+ .map(|path| {
+ acp::ToolCallContent::Content(acp::Content::new(
+ acp::ContentBlock::ResourceLink(acp::ResourceLink::new(
+ path.to_string_lossy(),
+ format!("file://{}", path.display()),
+ )),
+ ))
+ })
+ .collect(),
+ ),
+ );
Ok(FindPathToolOutput {
offset: input.offset,
@@ -152,15 +152,12 @@ impl AgentTool for ReadFileTool {
}
let file_path = input.path.clone();
+ let mut location = acp::ToolCallLocation::new(&abs_path);
+ if let Some(line) = input.start_line {
+ location = location.line(line.saturating_sub(1));
+ }
- event_stream.update_fields(ToolCallUpdateFields {
- locations: Some(vec![acp::ToolCallLocation {
- path: abs_path.clone(),
- line: input.start_line.map(|line| line.saturating_sub(1)),
- meta: None,
- }]),
- ..Default::default()
- });
+ event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![location]));
if image_store::is_image_file(&self.project, &project_path, cx) {
return cx.spawn(async move |cx| {
@@ -289,12 +286,9 @@ impl AgentTool for ReadFileTool {
text,
}
.to_string();
- event_stream.update_fields(ToolCallUpdateFields {
- content: Some(vec![acp::ToolCallContent::Content {
- content: markdown.into(),
- }]),
- ..Default::default()
- })
+ event_stream.update_fields(ToolCallUpdateFields::new().content(vec![
+ acp::ToolCallContent::Content(acp::Content::new(markdown)),
+ ]));
}
})?;
@@ -112,10 +112,9 @@ impl AgentTool for TerminalTool {
.await?;
let terminal_id = terminal.id(cx)?;
- event_stream.update_fields(acp::ToolCallUpdateFields {
- content: Some(vec![acp::ToolCallContent::Terminal { terminal_id }]),
- ..Default::default()
- });
+ event_stream.update_fields(acp::ToolCallUpdateFields::new().content(vec![
+ acp::ToolCallContent::Terminal(acp::Terminal::new(terminal_id)),
+ ]));
let exit_status = terminal.wait_for_exit(cx)?.await;
let output = terminal.current_output(cx)?;
@@ -43,10 +43,8 @@ impl AgentTool for ThinkingTool {
event_stream: ToolCallEventStream,
_cx: &mut App,
) -> Task<Result<String>> {
- event_stream.update_fields(acp::ToolCallUpdateFields {
- content: Some(vec![input.content.into()]),
- ..Default::default()
- });
+ event_stream
+ .update_fields(acp::ToolCallUpdateFields::new().content(vec![input.content.into()]));
Task::ready(Ok("Finished thinking.".to_string()))
}
}
@@ -76,10 +76,8 @@ impl AgentTool for WebSearchTool {
let response = match search_task.await {
Ok(response) => response,
Err(err) => {
- event_stream.update_fields(acp::ToolCallUpdateFields {
- title: Some("Web Search Failed".to_string()),
- ..Default::default()
- });
+ event_stream
+ .update_fields(acp::ToolCallUpdateFields::new().title("Web Search Failed"));
return Err(err);
}
};
@@ -107,26 +105,23 @@ fn emit_update(response: &WebSearchResponse, event_stream: &ToolCallEventStream)
} else {
format!("{} results", response.results.len())
};
- event_stream.update_fields(acp::ToolCallUpdateFields {
- title: Some(format!("Searched the web: {result_text}")),
- content: Some(
- response
- .results
- .iter()
- .map(|result| acp::ToolCallContent::Content {
- content: acp::ContentBlock::ResourceLink(acp::ResourceLink {
- name: result.title.clone(),
- uri: result.url.clone(),
- title: Some(result.title.clone()),
- description: Some(result.text.clone()),
- mime_type: None,
- annotations: None,
- size: None,
- meta: None,
- }),
- })
- .collect(),
- ),
- ..Default::default()
- });
+ event_stream.update_fields(
+ acp::ToolCallUpdateFields::new()
+ .title(format!("Searched the web: {result_text}"))
+ .content(
+ response
+ .results
+ .iter()
+ .map(|result| {
+ acp::ToolCallContent::Content(acp::Content::new(
+ acp::ContentBlock::ResourceLink(
+ acp::ResourceLink::new(result.title.clone(), result.url.clone())
+ .title(result.title.clone())
+ .description(result.text.clone()),
+ ),
+ ))
+ })
+ .collect(),
+ ),
+ );
}
@@ -76,7 +76,7 @@ pub async fn connect(
Ok(Rc::new(conn) as _)
}
-const MINIMUM_SUPPORTED_VERSION: acp::ProtocolVersion = acp::V1;
+const MINIMUM_SUPPORTED_VERSION: acp::ProtocolVersion = acp::ProtocolVersion::V1;
impl AcpConnection {
pub async fn stdio(
@@ -173,29 +173,27 @@ impl AcpConnection {
});
})?;
+ let mut client_info = acp::Implementation::new("zed", version);
+ if let Some(release_channel) = release_channel {
+ client_info = client_info.title(release_channel);
+ }
let response = connection
- .initialize(acp::InitializeRequest {
- protocol_version: acp::VERSION,
- client_capabilities: acp::ClientCapabilities {
- fs: acp::FileSystemCapability {
- read_text_file: true,
- write_text_file: true,
- meta: None,
- },
- terminal: true,
- meta: Some(serde_json::json!({
- // Experimental: Allow for rendering terminal output from the agents
- "terminal_output": true,
- "terminal-auth": true,
- })),
- },
- client_info: Some(acp::Implementation {
- name: "zed".to_owned(),
- title: release_channel.map(|c| c.to_owned()),
- version,
- }),
- meta: None,
- })
+ .initialize(
+ acp::InitializeRequest::new(acp::ProtocolVersion::V1)
+ .client_capabilities(
+ acp::ClientCapabilities::new()
+ .fs(acp::FileSystemCapability::new()
+ .read_text_file(true)
+ .write_text_file(true))
+ .terminal(true)
+ // Experimental: Allow for rendering terminal output from the agents
+ .meta(acp::Meta::from_iter([
+ ("terminal_output".into(), true.into()),
+ ("terminal-auth".into(), true.into()),
+ ])),
+ )
+ .client_info(client_info),
+ )
.await?;
if response.protocol_version < MINIMUM_SUPPORTED_VERSION {
@@ -253,14 +251,13 @@ impl AgentConnection for AcpConnection {
let default_model = self.default_model.clone();
let cwd = cwd.to_path_buf();
let context_server_store = project.read(cx).context_server_store().read(cx);
- let mcp_servers =
- if project.read(cx).is_local() {
- context_server_store
- .configured_server_ids()
- .iter()
- .filter_map(|id| {
- let configuration = context_server_store.configuration_for_server(id)?;
- match &*configuration {
+ let mcp_servers = if project.read(cx).is_local() {
+ context_server_store
+ .configured_server_ids()
+ .iter()
+ .filter_map(|id| {
+ let configuration = context_server_store.configuration_for_server(id)?;
+ match &*configuration {
project::context_server_store::ContextServerConfiguration::Custom {
command,
..
@@ -268,47 +265,41 @@ impl AgentConnection for AcpConnection {
| project::context_server_store::ContextServerConfiguration::Extension {
command,
..
- } => Some(acp::McpServer::Stdio {
- name: id.0.to_string(),
- command: command.path.clone(),
- args: command.args.clone(),
- env: if let Some(env) = command.env.as_ref() {
- env.iter()
- .map(|(name, value)| acp::EnvVariable {
- name: name.clone(),
- value: value.clone(),
- meta: None,
- })
- .collect()
- } else {
- vec![]
- },
- }),
+ } => Some(acp::McpServer::Stdio(
+ acp::McpServerStdio::new(id.0.to_string(), &command.path)
+ .args(command.args.clone())
+ .env(if let Some(env) = command.env.as_ref() {
+ env.iter()
+ .map(|(name, value)| acp::EnvVariable::new(name, value))
+ .collect()
+ } else {
+ vec![]
+ }),
+ )),
project::context_server_store::ContextServerConfiguration::Http {
url,
headers,
- } => Some(acp::McpServer::Http {
- name: id.0.to_string(),
- url: url.to_string(),
- headers: headers.iter().map(|(name, value)| acp::HttpHeader {
- name: name.clone(),
- value: value.clone(),
- meta: None,
- }).collect(),
- }),
+ } => Some(acp::McpServer::Http(
+ acp::McpServerHttp::new(id.0.to_string(), url.to_string()).headers(
+ headers
+ .iter()
+ .map(|(name, value)| acp::HttpHeader::new(name, value))
+ .collect(),
+ ),
+ )),
}
- })
- .collect()
- } else {
- // In SSH projects, the external agent is running on the remote
- // machine, and currently we only run MCP servers on the local
- // machine. So don't pass any MCP servers to the agent in that case.
- Vec::new()
- };
+ })
+ .collect()
+ } else {
+ // In SSH projects, the external agent is running on the remote
+ // machine, and currently we only run MCP servers on the local
+ // machine. So don't pass any MCP servers to the agent in that case.
+ Vec::new()
+ };
cx.spawn(async move |cx| {
let response = conn
- .new_session(acp::NewSessionRequest { mcp_servers, cwd, meta: None })
+ .new_session(acp::NewSessionRequest::new(cwd).mcp_servers(mcp_servers))
.await
.map_err(|err| {
if err.code == acp::ErrorCode::AUTH_REQUIRED.code {
@@ -341,11 +332,7 @@ impl AgentConnection for AcpConnection {
let modes = modes.clone();
let conn = conn.clone();
async move |_| {
- let result = conn.set_session_mode(acp::SetSessionModeRequest {
- session_id,
- mode_id: default_mode,
- meta: None,
- })
+ let result = conn.set_session_mode(acp::SetSessionModeRequest::new(session_id, default_mode))
.await.log_err();
if result.is_none() {
@@ -388,11 +375,7 @@ impl AgentConnection for AcpConnection {
let models = models.clone();
let conn = conn.clone();
async move |_| {
- let result = conn.set_session_model(acp::SetSessionModelRequest {
- session_id,
- model_id: default_model,
- meta: None,
- })
+ let result = conn.set_session_model(acp::SetSessionModelRequest::new(session_id, default_model))
.await.log_err();
if result.is_none() {
@@ -456,12 +439,8 @@ impl AgentConnection for AcpConnection {
fn authenticate(&self, method_id: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>> {
let conn = self.connection.clone();
cx.foreground_executor().spawn(async move {
- conn.authenticate(acp::AuthenticateRequest {
- method_id: method_id.clone(),
- meta: None,
- })
- .await?;
-
+ conn.authenticate(acp::AuthenticateRequest::new(method_id))
+ .await?;
Ok(())
})
}
@@ -515,10 +494,7 @@ impl AgentConnection for AcpConnection {
&& (details.contains("This operation was aborted")
|| details.contains("The user aborted a request"))
{
- Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::Cancelled,
- meta: None,
- })
+ Ok(acp::PromptResponse::new(acp::StopReason::Cancelled))
} else {
Err(anyhow!(details))
}
@@ -535,10 +511,7 @@ impl AgentConnection for AcpConnection {
session.suppress_abort_err = true;
}
let conn = self.connection.clone();
- let params = acp::CancelNotification {
- session_id: session_id.clone(),
- meta: None,
- };
+ let params = acp::CancelNotification::new(session_id.clone());
cx.foreground_executor()
.spawn(async move { conn.cancel(params).await })
.detach();
@@ -619,11 +592,7 @@ impl acp_thread::AgentSessionModes for AcpSessionModes {
let state = self.state.clone();
cx.foreground_executor().spawn(async move {
let result = connection
- .set_session_mode(acp::SetSessionModeRequest {
- session_id,
- mode_id,
- meta: None,
- })
+ .set_session_mode(acp::SetSessionModeRequest::new(session_id, mode_id))
.await;
if result.is_err() {
@@ -682,11 +651,7 @@ impl acp_thread::AgentModelSelector for AcpModelSelector {
let state = self.state.clone();
cx.foreground_executor().spawn(async move {
let result = connection
- .set_session_model(acp::SetSessionModelRequest {
- session_id,
- model_id,
- meta: None,
- })
+ .set_session_model(acp::SetSessionModelRequest::new(session_id, model_id))
.await;
if result.is_err() {
@@ -748,10 +713,7 @@ impl acp::Client for ClientDelegate {
let outcome = task.await;
- Ok(acp::RequestPermissionResponse {
- outcome,
- meta: None,
- })
+ Ok(acp::RequestPermissionResponse::new(outcome))
}
async fn write_text_file(
@@ -783,10 +745,7 @@ impl acp::Client for ClientDelegate {
let content = task.await?;
- Ok(acp::ReadTextFileResponse {
- content,
- meta: None,
- })
+ Ok(acp::ReadTextFileResponse::new(content))
}
async fn session_notification(
@@ -821,7 +780,7 @@ impl acp::Client for ClientDelegate {
if let Some(terminal_info) = meta.get("terminal_info") {
if let Some(id_str) = terminal_info.get("terminal_id").and_then(|v| v.as_str())
{
- let terminal_id = acp::TerminalId(id_str.into());
+ let terminal_id = acp::TerminalId::new(id_str);
let cwd = terminal_info
.get("cwd")
.and_then(|v| v.as_str().map(PathBuf::from));
@@ -837,7 +796,7 @@ impl acp::Client for ClientDelegate {
let lower = cx.new(|cx| builder.subscribe(cx));
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
- terminal_id: terminal_id.clone(),
+ terminal_id,
label: tc.title.clone(),
cwd,
output_byte_limit: None,
@@ -862,15 +821,12 @@ impl acp::Client for ClientDelegate {
if let Some(meta) = &tcu.meta {
if let Some(term_out) = meta.get("terminal_output") {
if let Some(id_str) = term_out.get("terminal_id").and_then(|v| v.as_str()) {
- let terminal_id = acp::TerminalId(id_str.into());
+ let terminal_id = acp::TerminalId::new(id_str);
if let Some(s) = term_out.get("data").and_then(|v| v.as_str()) {
let data = s.as_bytes().to_vec();
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.on_terminal_provider_event(
- TerminalProviderEvent::Output {
- terminal_id: terminal_id.clone(),
- data,
- },
+ TerminalProviderEvent::Output { terminal_id, data },
cx,
);
});
@@ -881,21 +837,19 @@ impl acp::Client for ClientDelegate {
// terminal_exit
if let Some(term_exit) = meta.get("terminal_exit") {
if let Some(id_str) = term_exit.get("terminal_id").and_then(|v| v.as_str()) {
- let terminal_id = acp::TerminalId(id_str.into());
- let status = acp::TerminalExitStatus {
- exit_code: term_exit
- .get("exit_code")
- .and_then(|v| v.as_u64())
- .map(|i| i as u32),
- signal: term_exit
- .get("signal")
- .and_then(|v| v.as_str().map(|s| s.to_string())),
- meta: None,
- };
+ let terminal_id = acp::TerminalId::new(id_str);
+ let mut status = acp::TerminalExitStatus::new();
+ if let Some(code) = term_exit.get("exit_code").and_then(|v| v.as_u64()) {
+ status = status.exit_code(code as u32)
+ }
+ if let Some(signal) = term_exit.get("signal").and_then(|v| v.as_str()) {
+ status = status.signal(signal);
+ }
+
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
- terminal_id: terminal_id.clone(),
+ terminal_id,
status,
},
cx,
@@ -932,7 +886,7 @@ impl acp::Client for ClientDelegate {
// Register with renderer
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {
thread.register_terminal_created(
- acp::TerminalId(uuid::Uuid::new_v4().to_string().into()),
+ acp::TerminalId::new(uuid::Uuid::new_v4().to_string()),
format!("{} {}", args.command, args.args.join(" ")),
args.cwd.clone(),
args.output_byte_limit,
@@ -942,10 +896,7 @@ impl acp::Client for ClientDelegate {
})?;
let terminal_id =
terminal_entity.read_with(&self.cx, |terminal, _| terminal.id().clone())?;
- Ok(acp::CreateTerminalResponse {
- terminal_id,
- meta: None,
- })
+ Ok(acp::CreateTerminalResponse::new(terminal_id))
}
async fn kill_terminal_command(
@@ -1006,10 +957,7 @@ impl acp::Client for ClientDelegate {
})??
.await;
- Ok(acp::WaitForTerminalExitResponse {
- exit_status,
- meta: None,
- })
+ Ok(acp::WaitForTerminalExitResponse::new(exit_status))
}
}
@@ -41,7 +41,7 @@ impl AgentServer for ClaudeCode {
settings
.as_ref()
- .and_then(|s| s.default_mode.clone().map(|m| acp::SessionModeId(m.into())))
+ .and_then(|s| s.default_mode.clone().map(acp::SessionModeId::new))
}
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
@@ -62,7 +62,7 @@ impl AgentServer for ClaudeCode {
settings
.as_ref()
- .and_then(|s| s.default_model.clone().map(|m| acp::ModelId(m.into())))
+ .and_then(|s| s.default_model.clone().map(acp::ModelId::new))
}
fn set_default_model(&self, model_id: Option<acp::ModelId>, fs: Arc<dyn Fs>, cx: &mut App) {
@@ -42,7 +42,7 @@ impl AgentServer for Codex {
settings
.as_ref()
- .and_then(|s| s.default_mode.clone().map(|m| acp::SessionModeId(m.into())))
+ .and_then(|s| s.default_mode.clone().map(acp::SessionModeId::new))
}
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
@@ -63,7 +63,7 @@ impl AgentServer for Codex {
settings
.as_ref()
- .and_then(|s| s.default_model.clone().map(|m| acp::ModelId(m.into())))
+ .and_then(|s| s.default_model.clone().map(acp::ModelId::new))
}
fn set_default_model(&self, model_id: Option<acp::ModelId>, fs: Arc<dyn Fs>, cx: &mut App) {
@@ -44,7 +44,7 @@ impl crate::AgentServer for CustomAgentServer {
settings
.as_ref()
- .and_then(|s| s.default_mode().map(|m| acp::SessionModeId(m.into())))
+ .and_then(|s| s.default_mode().map(acp::SessionModeId::new))
}
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
@@ -80,7 +80,7 @@ impl crate::AgentServer for CustomAgentServer {
settings
.as_ref()
- .and_then(|s| s.default_model().map(|m| acp::ModelId(m.into())))
+ .and_then(|s| s.default_model().map(acp::ModelId::new))
}
fn set_default_model(&self, model_id: Option<acp::ModelId>, fs: Arc<dyn Fs>, cx: &mut App) {
@@ -82,26 +82,9 @@ where
.update(cx, |thread, cx| {
thread.send(
vec![
- acp::ContentBlock::Text(acp::TextContent {
- text: "Read the file ".into(),
- annotations: None,
- meta: None,
- }),
- acp::ContentBlock::ResourceLink(acp::ResourceLink {
- uri: "foo.rs".into(),
- name: "foo.rs".into(),
- annotations: None,
- description: None,
- mime_type: None,
- size: None,
- title: None,
- meta: None,
- }),
- acp::ContentBlock::Text(acp::TextContent {
- text: " and tell me what the content of the println! is".into(),
- annotations: None,
- meta: None,
- }),
+ "Read the file ".into(),
+ acp::ContentBlock::ResourceLink(acp::ResourceLink::new("foo.rs", "foo.rs")),
+ " and tell me what the content of the println! is".into(),
],
cx,
)
@@ -429,7 +412,7 @@ macro_rules! common_e2e_tests {
async fn tool_call_with_permission(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_tool_call_with_permission(
$server,
- ::agent_client_protocol::PermissionOptionId($allow_option_id.into()),
+ ::agent_client_protocol::PermissionOptionId::new($allow_option_id),
cx,
)
.await;
@@ -432,24 +432,11 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
- let tool_call = acp::ToolCall {
- id: acp::ToolCallId("tool".into()),
- title: "Tool call".into(),
- kind: acp::ToolKind::Other,
- status: acp::ToolCallStatus::InProgress,
- content: vec![acp::ToolCallContent::Diff {
- diff: acp::Diff {
- path: "/project/hello.txt".into(),
- old_text: Some("hi world".into()),
- new_text: "hello world".into(),
- meta: None,
- },
- }],
- locations: vec![],
- raw_input: None,
- raw_output: None,
- meta: None,
- };
+ let tool_call = acp::ToolCall::new("tool", "Tool call")
+ .status(acp::ToolCallStatus::InProgress)
+ .content(vec![acp::ToolCallContent::Diff(
+ acp::Diff::new("/project/hello.txt", "hello world").old_text("hi world"),
+ )]);
let connection = Rc::new(StubAgentConnection::new());
let thread = cx
.update(|_, cx| {
@@ -225,8 +225,13 @@ impl MessageEditor {
.iter()
.find(|command| command.name == command_name)?;
- let acp::AvailableCommandInput::Unstructured { mut hint } =
- available_command.input.clone()?;
+ let acp::AvailableCommandInput::Unstructured(acp::UnstructuredCommandInput {
+ mut hint,
+ ..
+ }) = available_command.input.clone()?
+ else {
+ return None;
+ };
let mut hint_pos = MultiBufferOffset(parsed_command.source_range.end) + 1usize;
if hint_pos > snapshot.len() {
@@ -403,34 +408,28 @@ impl MessageEditor {
} => {
all_tracked_buffers.extend(tracked_buffers.iter().cloned());
if supports_embedded_context {
- acp::ContentBlock::Resource(acp::EmbeddedResource {
- annotations: None,
- resource:
- acp::EmbeddedResourceResource::TextResourceContents(
- acp::TextResourceContents {
- mime_type: None,
- text: content.clone(),
- uri: uri.to_uri().to_string(),
- meta: None,
- },
+ acp::ContentBlock::Resource(acp::EmbeddedResource::new(
+ acp::EmbeddedResourceResource::TextResourceContents(
+ acp::TextResourceContents::new(
+ content.clone(),
+ uri.to_uri().to_string(),
),
- meta: None,
- })
+ ),
+ ))
} else {
- acp::ContentBlock::ResourceLink(acp::ResourceLink {
- name: uri.name(),
- uri: uri.to_uri().to_string(),
- annotations: None,
- description: None,
- mime_type: None,
- size: None,
- title: None,
- meta: None,
- })
+ acp::ContentBlock::ResourceLink(acp::ResourceLink::new(
+ uri.name(),
+ uri.to_uri().to_string(),
+ ))
}
}
Mention::Image(mention_image) => {
- let uri = match uri {
+ let mut image = acp::ImageContent::new(
+ mention_image.data.clone(),
+ mention_image.format.mime_type(),
+ );
+
+ if let Some(uri) = match uri {
MentionUri::File { .. } => Some(uri.to_uri().to_string()),
MentionUri::PastedImage => None,
other => {
@@ -440,25 +439,14 @@ impl MessageEditor {
);
None
}
+ } {
+ image = image.uri(uri)
};
- acp::ContentBlock::Image(acp::ImageContent {
- annotations: None,
- data: mention_image.data.to_string(),
- mime_type: mention_image.format.mime_type().into(),
- uri,
- meta: None,
- })
+ acp::ContentBlock::Image(image)
}
- Mention::Link => acp::ContentBlock::ResourceLink(acp::ResourceLink {
- name: uri.name(),
- uri: uri.to_uri().to_string(),
- annotations: None,
- description: None,
- mime_type: None,
- size: None,
- title: None,
- meta: None,
- }),
+ Mention::Link => acp::ContentBlock::ResourceLink(
+ acp::ResourceLink::new(uri.name(), uri.to_uri().to_string()),
+ ),
};
chunks.push(chunk);
ix = crease_range.end.0;
@@ -746,8 +734,7 @@ impl MessageEditor {
uri,
data,
mime_type,
- annotations: _,
- meta: _,
+ ..
}) => {
let mention_uri = if let Some(uri) = uri {
MentionUri::parse(&uri, path_style)
@@ -773,7 +760,7 @@ impl MessageEditor {
}),
));
}
- acp::ContentBlock::Audio(_) | acp::ContentBlock::Resource(_) => {}
+ _ => {}
}
}
@@ -1092,12 +1079,7 @@ mod tests {
assert!(error_message.contains("Available commands: none"));
// Now simulate Claude providing its list of available commands (which doesn't include file)
- available_commands.replace(vec![acp::AvailableCommand {
- name: "help".to_string(),
- description: "Get help".to_string(),
- input: None,
- meta: None,
- }]);
+ available_commands.replace(vec![acp::AvailableCommand::new("help", "Get help")]);
// Test that unsupported slash commands trigger an error when we have a list of available commands
editor.update_in(cx, |editor, window, cx| {
@@ -1211,20 +1193,12 @@ mod tests {
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let available_commands = Rc::new(RefCell::new(vec![
- acp::AvailableCommand {
- name: "quick-math".to_string(),
- description: "2 + 2 = 4 - 1 = 3".to_string(),
- input: None,
- meta: None,
- },
- acp::AvailableCommand {
- name: "say-hello".to_string(),
- description: "Say hello to whoever you want".to_string(),
- input: Some(acp::AvailableCommandInput::Unstructured {
- hint: "<name>".to_string(),
- }),
- meta: None,
- },
+ acp::AvailableCommand::new("quick-math", "2 + 2 = 4 - 1 = 3"),
+ acp::AvailableCommand::new("say-hello", "Say hello to whoever you want").input(
+ acp::AvailableCommandInput::Unstructured(acp::UnstructuredCommandInput::new(
+ "<name>",
+ )),
+ ),
]));
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -1504,12 +1478,12 @@ mod tests {
editor.set_text("", window, cx);
});
- prompt_capabilities.replace(acp::PromptCapabilities {
- image: true,
- audio: true,
- embedded_context: true,
- meta: None,
- });
+ prompt_capabilities.replace(
+ acp::PromptCapabilities::new()
+ .image(true)
+ .audio(true)
+ .embedded_context(true),
+ );
cx.simulate_input("Lorem ");
@@ -1960,11 +1934,9 @@ mod tests {
cx,
);
// Enable embedded context so files are actually included
- editor.prompt_capabilities.replace(acp::PromptCapabilities {
- embedded_context: true,
- meta: None,
- ..Default::default()
- });
+ editor
+ .prompt_capabilities
+ .replace(acp::PromptCapabilities::new().embedded_context(true));
editor
})
});
@@ -2043,7 +2015,7 @@ mod tests {
// Create a thread metadata to insert as summary
let thread_metadata = agent::DbThreadMetadata {
- id: acp::SessionId("thread-123".into()),
+ id: acp::SessionId::new("thread-123"),
title: "Previous Conversation".into(),
updated_at: chrono::Utc::now(),
};
@@ -2150,14 +2122,7 @@ mod tests {
.await
.unwrap();
- assert_eq!(
- content,
- vec![acp::ContentBlock::Text(acp::TextContent {
- text: "γγ¦hello world".into(),
- annotations: None,
- meta: None
- })]
- );
+ assert_eq!(content, vec!["γγ¦hello world".into()]);
}
#[gpui::test]
@@ -2236,38 +2201,24 @@ mod tests {
.0;
let main_rs_uri = if cfg!(windows) {
- "file:///C:/project/src/main.rs".to_string()
+ "file:///C:/project/src/main.rs"
} else {
- "file:///project/src/main.rs".to_string()
+ "file:///project/src/main.rs"
};
// When embedded context is `false` we should get a resource link
pretty_assertions::assert_eq!(
content,
vec![
- acp::ContentBlock::Text(acp::TextContent {
- text: "What is in ".to_string(),
- annotations: None,
- meta: None
- }),
- acp::ContentBlock::ResourceLink(acp::ResourceLink {
- uri: main_rs_uri.clone(),
- name: "main.rs".to_string(),
- annotations: None,
- meta: None,
- description: None,
- mime_type: None,
- size: None,
- title: None,
- })
+ "What is in ".into(),
+ acp::ContentBlock::ResourceLink(acp::ResourceLink::new("main.rs", main_rs_uri))
]
);
message_editor.update(cx, |editor, _cx| {
- editor.prompt_capabilities.replace(acp::PromptCapabilities {
- embedded_context: true,
- ..Default::default()
- })
+ editor
+ .prompt_capabilities
+ .replace(acp::PromptCapabilities::new().embedded_context(true))
});
let content = message_editor
@@ -2280,23 +2231,12 @@ mod tests {
pretty_assertions::assert_eq!(
content,
vec![
- acp::ContentBlock::Text(acp::TextContent {
- text: "What is in ".to_string(),
- annotations: None,
- meta: None
- }),
- acp::ContentBlock::Resource(acp::EmbeddedResource {
- resource: acp::EmbeddedResourceResource::TextResourceContents(
- acp::TextResourceContents {
- text: file_content.to_string(),
- uri: main_rs_uri,
- mime_type: None,
- meta: None
- }
- ),
- annotations: None,
- meta: None
- })
+ "What is in ".into(),
+ acp::ContentBlock::Resource(acp::EmbeddedResource::new(
+ acp::EmbeddedResourceResource::TextResourceContents(
+ acp::TextResourceContents::new(file_content, main_rs_uri)
+ )
+ ))
]
);
}
@@ -464,7 +464,7 @@ mod tests {
models
.into_iter()
.map(|model| acp_thread::AgentModelInfo {
- id: acp::ModelId(model.to_string().into()),
+ id: acp::ModelId::new(model.to_string()),
name: model.to_string().into(),
description: None,
icon: None,
@@ -1476,18 +1476,8 @@ impl AcpThreadView {
.iter()
.any(|method| method.id.0.as_ref() == "claude-login")
{
- available_commands.push(acp::AvailableCommand {
- name: "login".to_owned(),
- description: "Authenticate".to_owned(),
- input: None,
- meta: None,
- });
- available_commands.push(acp::AvailableCommand {
- name: "logout".to_owned(),
- description: "Authenticate".to_owned(),
- input: None,
- meta: None,
- });
+ available_commands.push(acp::AvailableCommand::new("login", "Authenticate"));
+ available_commands.push(acp::AvailableCommand::new("logout", "Authenticate"));
}
let has_commands = !available_commands.is_empty();
@@ -2562,7 +2552,7 @@ impl AcpThreadView {
acp::ToolKind::Think => IconName::ToolThink,
acp::ToolKind::Fetch => IconName::ToolWeb,
acp::ToolKind::SwitchMode => IconName::ArrowRightLeft,
- acp::ToolKind::Other => IconName::ToolHammer,
+ acp::ToolKind::Other | _ => IconName::ToolHammer,
})
}
.size(IconSize::Small)
@@ -2814,7 +2804,7 @@ impl AcpThreadView {
})
.gap_0p5()
.children(options.iter().map(move |option| {
- let option_id = SharedString::from(option.id.0.clone());
+ let option_id = SharedString::from(option.option_id.0.clone());
Button::new((option_id, entry_ix), option.name.clone())
.map(|this| {
let (this, action) = match option.kind {
@@ -2830,7 +2820,7 @@ impl AcpThreadView {
this.icon(IconName::Close).icon_color(Color::Error),
Some(&RejectOnce as &dyn Action),
),
- acp::PermissionOptionKind::RejectAlways => {
+ acp::PermissionOptionKind::RejectAlways | _ => {
(this.icon(IconName::Close).icon_color(Color::Error), None)
}
};
@@ -2855,7 +2845,7 @@ impl AcpThreadView {
.label_size(LabelSize::Small)
.on_click(cx.listener({
let tool_call_id = tool_call_id.clone();
- let option_id = option.id.clone();
+ let option_id = option.option_id.clone();
let option_kind = option.kind;
move |this, _, window, cx| {
this.authorize_tool_call(
@@ -3543,7 +3533,7 @@ impl AcpThreadView {
);
this.authenticate(
- acp::AuthMethodId(method_id.clone()),
+ acp::AuthMethodId::new(method_id.clone()),
window,
cx,
)
@@ -3837,10 +3827,6 @@ impl AcpThreadView {
.text_xs()
.text_color(cx.theme().colors().text_muted)
.child(match entry.status {
- acp::PlanEntryStatus::Pending => Icon::new(IconName::TodoPending)
- .size(IconSize::Small)
- .color(Color::Muted)
- .into_any_element(),
acp::PlanEntryStatus::InProgress => {
Icon::new(IconName::TodoProgress)
.size(IconSize::Small)
@@ -3854,6 +3840,12 @@ impl AcpThreadView {
.color(Color::Success)
.into_any_element()
}
+ acp::PlanEntryStatus::Pending | _ => {
+ Icon::new(IconName::TodoPending)
+ .size(IconSize::Small)
+ .color(Color::Muted)
+ .into_any_element()
+ }
})
.child(MarkdownElement::new(
entry.content.clone(),
@@ -4427,7 +4419,7 @@ impl AcpThreadView {
self.authorize_tool_call(
tool_call.id.clone(),
- option.id.clone(),
+ option.option_id.clone(),
option.kind,
window,
cx,
@@ -6243,27 +6235,18 @@ pub(crate) mod tests {
async fn test_notification_for_tool_authorization(cx: &mut TestAppContext) {
init_test(cx);
- let tool_call_id = acp::ToolCallId("1".into());
- let tool_call = acp::ToolCall {
- id: tool_call_id.clone(),
- title: "Label".into(),
- kind: acp::ToolKind::Edit,
- status: acp::ToolCallStatus::Pending,
- content: vec!["hi".into()],
- locations: vec![],
- raw_input: None,
- raw_output: None,
- meta: None,
- };
+ let tool_call_id = acp::ToolCallId::new("1");
+ let tool_call = acp::ToolCall::new(tool_call_id.clone(), "Label")
+ .kind(acp::ToolKind::Edit)
+ .content(vec!["hi".into()]);
let connection =
StubAgentConnection::new().with_permission_requests(HashMap::from_iter([(
tool_call_id,
- vec![acp::PermissionOption {
- id: acp::PermissionOptionId("1".into()),
- name: "Allow".into(),
- kind: acp::PermissionOptionKind::AllowOnce,
- meta: None,
- }],
+ vec![acp::PermissionOption::new(
+ "1".into(),
+ "Allow",
+ acp::PermissionOptionKind::AllowOnce,
+ )],
)]));
connection.set_next_prompt_updates(vec![acp::SessionUpdate::ToolCall(tool_call)]);
@@ -6482,10 +6465,7 @@ pub(crate) mod tests {
fn default_response() -> Self {
let conn = StubAgentConnection::new();
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
- acp::ContentChunk {
- content: "Default response".into(),
- meta: None,
- },
+ acp::ContentChunk::new("Default response".into()),
)]);
Self::new(conn)
}
@@ -6542,13 +6522,13 @@ pub(crate) mod tests {
self,
project,
action_log,
- SessionId("test".into()),
- watch::Receiver::constant(acp::PromptCapabilities {
- image: true,
- audio: true,
- embedded_context: true,
- meta: None,
- }),
+ SessionId::new("test"),
+ watch::Receiver::constant(
+ acp::PromptCapabilities::new()
+ .image(true)
+ .audio(true)
+ .embedded_context(true),
+ ),
cx,
)
})))
@@ -6606,13 +6586,13 @@ pub(crate) mod tests {
self,
project,
action_log,
- SessionId("test".into()),
- watch::Receiver::constant(acp::PromptCapabilities {
- image: true,
- audio: true,
- embedded_context: true,
- meta: None,
- }),
+ SessionId::new("test"),
+ watch::Receiver::constant(
+ acp::PromptCapabilities::new()
+ .image(true)
+ .audio(true)
+ .embedded_context(true),
+ ),
cx,
)
})))
@@ -6636,10 +6616,7 @@ pub(crate) mod tests {
_params: acp::PromptRequest,
_cx: &mut App,
) -> Task<gpui::Result<acp::PromptResponse>> {
- Task::ready(Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::Refusal,
- meta: None,
- }))
+ Task::ready(Ok(acp::PromptResponse::new(acp::StopReason::Refusal)))
}
fn cancel(&self, _session_id: &acp::SessionId, _cx: &mut App) {
@@ -6707,24 +6684,14 @@ pub(crate) mod tests {
.unwrap();
// First user message
- connection.set_next_prompt_updates(vec![acp::SessionUpdate::ToolCall(acp::ToolCall {
- id: acp::ToolCallId("tool1".into()),
- title: "Edit file 1".into(),
- kind: acp::ToolKind::Edit,
- status: acp::ToolCallStatus::Completed,
- content: vec![acp::ToolCallContent::Diff {
- diff: acp::Diff {
- path: "/project/test1.txt".into(),
- old_text: Some("old content 1".into()),
- new_text: "new content 1".into(),
- meta: None,
- },
- }],
- locations: vec![],
- raw_input: None,
- raw_output: None,
- meta: None,
- })]);
+ connection.set_next_prompt_updates(vec![acp::SessionUpdate::ToolCall(
+ acp::ToolCall::new("tool1", "Edit file 1")
+ .kind(acp::ToolKind::Edit)
+ .status(acp::ToolCallStatus::Completed)
+ .content(vec![acp::ToolCallContent::Diff(
+ acp::Diff::new("/project/test1.txt", "new content 1").old_text("old content 1"),
+ )]),
+ )]);
thread
.update(cx, |thread, cx| thread.send_raw("Give me a diff", cx))
@@ -6750,24 +6717,14 @@ pub(crate) mod tests {
});
// Second user message
- connection.set_next_prompt_updates(vec![acp::SessionUpdate::ToolCall(acp::ToolCall {
- id: acp::ToolCallId("tool2".into()),
- title: "Edit file 2".into(),
- kind: acp::ToolKind::Edit,
- status: acp::ToolCallStatus::Completed,
- content: vec![acp::ToolCallContent::Diff {
- diff: acp::Diff {
- path: "/project/test2.txt".into(),
- old_text: Some("old content 2".into()),
- new_text: "new content 2".into(),
- meta: None,
- },
- }],
- locations: vec![],
- raw_input: None,
- raw_output: None,
- meta: None,
- })]);
+ connection.set_next_prompt_updates(vec![acp::SessionUpdate::ToolCall(
+ acp::ToolCall::new("tool2", "Edit file 2")
+ .kind(acp::ToolKind::Edit)
+ .status(acp::ToolCallStatus::Completed)
+ .content(vec![acp::ToolCallContent::Diff(
+ acp::Diff::new("/project/test2.txt", "new content 2").old_text("old content 2"),
+ )]),
+ )]);
thread
.update(cx, |thread, cx| thread.send_raw("Another one", cx))
@@ -6841,14 +6798,7 @@ pub(crate) mod tests {
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
- acp::ContentChunk {
- content: acp::ContentBlock::Text(acp::TextContent {
- text: "Response".into(),
- annotations: None,
- meta: None,
- }),
- meta: None,
- },
+ acp::ContentChunk::new("Response".into()),
)]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
@@ -6934,14 +6884,7 @@ pub(crate) mod tests {
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
- acp::ContentChunk {
- content: acp::ContentBlock::Text(acp::TextContent {
- text: "Response".into(),
- annotations: None,
- meta: None,
- }),
- meta: None,
- },
+ acp::ContentChunk::new("Response".into()),
)]);
let (thread_view, cx) =
@@ -6981,14 +6924,7 @@ pub(crate) mod tests {
// Send
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
- acp::ContentChunk {
- content: acp::ContentBlock::Text(acp::TextContent {
- text: "New Response".into(),
- annotations: None,
- meta: None,
- }),
- meta: None,
- },
+ acp::ContentChunk::new("New Response".into()),
)]);
user_message_editor.update_in(cx, |_editor, window, cx| {
@@ -7076,14 +7012,7 @@ pub(crate) mod tests {
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
- acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
- content: acp::ContentBlock::Text(acp::TextContent {
- text: "Response".into(),
- annotations: None,
- meta: None,
- }),
- meta: None,
- }),
+ acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new("Response".into())),
cx,
);
connection.end_turn(session_id, acp::StopReason::EndTurn);
@@ -7135,10 +7064,9 @@ pub(crate) mod tests {
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
- acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
- content: "Message 1 resp".into(),
- meta: None,
- }),
+ acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new(
+ "Message 1 resp".into(),
+ )),
cx,
);
});
@@ -7172,10 +7100,7 @@ pub(crate) mod tests {
// Simulate a response sent after beginning to cancel
connection.send_update(
session_id.clone(),
- acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
- content: "onse".into(),
- meta: None,
- }),
+ acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new("onse".into())),
cx,
);
});
@@ -7206,10 +7131,9 @@ pub(crate) mod tests {
cx.update(|_, cx| {
connection.send_update(
session_id.clone(),
- acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
- content: "Message 2 response".into(),
- meta: None,
- }),
+ acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk::new(
+ "Message 2 response".into(),
+ )),
cx,
);
connection.end_turn(session_id.clone(), acp::StopReason::EndTurn);
@@ -7248,14 +7172,7 @@ pub(crate) mod tests {
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
- acp::ContentChunk {
- content: acp::ContentBlock::Text(acp::TextContent {
- text: "Response".into(),
- annotations: None,
- meta: None,
- }),
- meta: None,
- },
+ acp::ContentChunk::new("Response".into()),
)]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
@@ -7334,14 +7251,7 @@ pub(crate) mod tests {
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
- acp::ContentChunk {
- content: acp::ContentBlock::Text(acp::TextContent {
- text: "Response".into(),
- annotations: None,
- meta: None,
- }),
- meta: None,
- },
+ acp::ContentChunk::new("Response".into()),
)]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
@@ -261,7 +261,7 @@ impl ExampleContext {
.expect("Unknown tool_name content in meta");
tool_uses_by_id.insert(
- tool_call.id,
+ tool_call.tool_call_id,
ToolUse {
name: tool_name.to_string(),
value: tool_call.raw_input.unwrap_or_default(),
@@ -277,7 +277,9 @@ impl ExampleContext {
ThreadEvent::ToolCallUpdate(tool_call_update) => {
if let acp_thread::ToolCallUpdate::UpdateFields(update) = tool_call_update {
if let Some(raw_input) = update.fields.raw_input {
- if let Some(tool_use) = tool_uses_by_id.get_mut(&update.id) {
+ if let Some(tool_use) =
+ tool_uses_by_id.get_mut(&update.tool_call_id)
+ {
tool_use.value = raw_input;
}
}
@@ -290,7 +292,7 @@ impl ExampleContext {
update.fields.status == Some(acp::ToolCallStatus::Completed);
let tool_use = tool_uses_by_id
- .remove(&update.id)
+ .remove(&update.tool_call_id)
.expect("Unrecognized tool call completed");
let log_message = if succeeded {
@@ -337,10 +339,7 @@ impl ExampleContext {
acp::StopReason::MaxTurnRequests => {
return Err(anyhow!("Exceeded maximum turn requests"));
}
- acp::StopReason::Refusal => {
- return Err(anyhow!("Refusal"));
- }
- acp::StopReason::Cancelled => return Err(anyhow!("Cancelled")),
+ stop_reason => return Err(anyhow!("{stop_reason:?}")),
},
}
}
@@ -303,13 +303,12 @@ impl ExampleInstance {
let context_server_registry = cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
let thread = if let Some(json) = &meta.existing_thread_json {
- let session_id = acp::SessionId(
+ let session_id = acp::SessionId::new(
rand::rng()
.sample_iter(&distr::Alphanumeric)
.take(7)
.map(char::from)
- .collect::<String>()
- .into(),
+ .collect::<String>(),
);
let db_thread = agent::DbThread::from_json(json.as_bytes()).expect("Can't read serialized thread");
@@ -640,7 +639,7 @@ impl agent::ThreadEnvironment for EvalThreadEnvironment {
cx.spawn(async move |cx| {
let language_registry =
project.read_with(cx, |project, _cx| project.languages().clone())?;
- let id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
+ let id = acp::TerminalId::new(uuid::Uuid::new_v4().to_string());
let terminal =
acp_thread::create_terminal_entity(command, &[], vec![], cwd.clone(), &project, cx)
.await?;