Compare commits

...

2 Commits

Author SHA1 Message Date
Bennet Bo Fenner
be8bf13de3 Fix tests 2026-01-01 14:31:26 +01:00
Bennet Bo Fenner
e242c1abd4 agent_ui: Show parameters when agent calls MCP server tool 2026-01-01 14:18:04 +01:00
5 changed files with 74 additions and 10 deletions

View File

@@ -188,6 +188,7 @@ pub struct ToolCall {
pub id: acp::ToolCallId,
pub label: Entity<Markdown>,
pub kind: acp::ToolKind,
pub source: ToolSource,
pub content: Vec<ToolCallContent>,
pub status: ToolCallStatus,
pub locations: Vec<acp::ToolCallLocation>,
@@ -224,6 +225,13 @@ impl ToolCall {
}
}
let tool_source = tool_call
.meta
.as_ref()
.and_then(|meta| meta.get("tool_source"))
.and_then(|v| serde_json::from_value::<ToolSource>(v.clone()).ok())
.unwrap_or_else(|| ToolSource::BuiltIn);
let raw_input_markdown = tool_call
.raw_input
.as_ref()
@@ -234,6 +242,7 @@ impl ToolCall {
label: cx
.new(|cx| Markdown::new(title.into(), Some(language_registry.clone()), None, cx)),
kind: tool_call.kind,
source: tool_source,
content,
locations: tool_call.locations,
resolved_locations: Vec::default(),
@@ -426,6 +435,15 @@ impl From<&ResolvedLocation> for AgentLocation {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolSource {
/// Built-in tool
BuiltIn,
/// Tool coming from an MCP server
Mcp,
}
#[derive(Debug)]
pub enum ToolCallStatus {
/// The tool call hasn't started running yet, but we start showing it to
@@ -1359,6 +1377,7 @@ impl AcpThread {
id: update.id().clone(),
label: cx.new(|cx| Markdown::new("Tool call not found".into(), None, None, cx)),
kind: acp::ToolKind::Fetch,
source: ToolSource::BuiltIn,
content: vec![ToolCallContent::ContentBlock(ContentBlock::new(
"Tool call not found".into(),
&languages,

View File

@@ -2268,10 +2268,10 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
acp::ToolCall::new("1", "Thinking")
.kind(acp::ToolKind::Think)
.raw_input(json!({}))
.meta(acp::Meta::from_iter([(
"tool_name".into(),
"thinking".into()
)]))
.meta(acp::Meta::from_iter([
("tool_name".into(), "thinking".into()),
("tool_source".into(), "built_in".into())
]))
);
let update = expect_tool_call_update_fields(&mut events).await;
assert_eq!(

View File

@@ -5,7 +5,7 @@ use crate::{
RestoreFileFromDiskTool, SaveFileTool, SystemPromptTemplate, Template, Templates, TerminalTool,
ThinkingTool, WebSearchTool,
};
use acp_thread::{MentionUri, UserMessageId};
use acp_thread::{MentionUri, ToolSource, UserMessageId};
use action_log::ActionLog;
use agent_client_protocol as acp;
@@ -756,6 +756,7 @@ impl Thread {
stream.send_tool_call(
&tool_use.id,
&tool_use.name,
tool.source(),
title,
kind,
tool_use.input.clone(),
@@ -1589,9 +1590,11 @@ impl Thread {
let tool = self.tool(tool_use.name.as_ref());
let mut title = SharedString::from(&tool_use.name);
let mut kind = acp::ToolKind::Other;
let mut source = acp_thread::ToolSource::BuiltIn;
if let Some(tool) = tool.as_ref() {
title = tool.initial_title(tool_use.input.clone(), cx);
kind = tool.kind();
source = tool.source();
}
// Ensure the last message ends in the current tool use
@@ -1613,6 +1616,7 @@ impl Thread {
event_stream.send_tool_call(
&tool_use.id,
&tool_use.name,
source,
title,
kind,
tool_use.input.clone(),
@@ -2320,6 +2324,9 @@ pub trait AnyAgentTool {
fn name(&self) -> SharedString;
fn description(&self) -> SharedString;
fn kind(&self) -> acp::ToolKind;
fn source(&self) -> ToolSource {
ToolSource::BuiltIn
}
fn initial_title(&self, input: serde_json::Value, _cx: &mut App) -> SharedString;
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value>;
fn supports_provider(&self, _provider: &LanguageModelProviderId) -> bool {
@@ -2429,6 +2436,7 @@ impl ThreadEventStream {
&self,
id: &LanguageModelToolUseId,
tool_name: &str,
tool_source: ToolSource,
title: SharedString,
kind: acp::ToolKind,
input: serde_json::Value,
@@ -2437,6 +2445,7 @@ impl ThreadEventStream {
.unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call(
id,
tool_name,
tool_source,
title.to_string(),
kind,
input,
@@ -2447,6 +2456,7 @@ impl ThreadEventStream {
fn initial_tool_call(
id: &LanguageModelToolUseId,
tool_name: &str,
tool_source: ToolSource,
title: String,
kind: acp::ToolKind,
input: serde_json::Value,
@@ -2454,10 +2464,13 @@ impl ThreadEventStream {
acp::ToolCall::new(id.to_string(), title)
.kind(kind)
.raw_input(input)
.meta(acp::Meta::from_iter([(
"tool_name".into(),
tool_name.into(),
)]))
.meta(acp::Meta::from_iter([
("tool_name".into(), tool_name.into()),
(
"tool_source".into(),
serde_json::to_value(tool_source).unwrap(),
),
]))
}
fn update_tool_call_fields(

View File

@@ -292,6 +292,10 @@ impl AnyAgentTool for ContextServerTool {
self.tool.name.clone().into()
}
fn source(&self) -> acp_thread::ToolSource {
acp_thread::ToolSource::Mcp
}
fn description(&self) -> SharedString {
self.tool.description.clone().unwrap_or_default().into()
}

View File

@@ -1,7 +1,7 @@
use acp_thread::{
AcpThread, AcpThreadEvent, AgentThreadEntry, AssistantMessage, AssistantMessageChunk,
AuthRequired, LoadError, MentionUri, RetryStatus, ThreadStatus, ToolCall, ToolCallContent,
ToolCallStatus, UserMessageId,
ToolCallStatus, ToolSource, UserMessageId,
};
use acp_thread::{AgentConnection, Plan};
use action_log::{ActionLog, ActionLogTelemetry};
@@ -2553,6 +2553,30 @@ impl AcpThreadView {
.into_any_element()
},
))
.map(|this| {
if should_show_tool_call_input(tool_call)
&& let Some(raw_input) = tool_call.raw_input_markdown.clone()
{
this.child(
v_flex()
.gap_1()
.p_2()
.child(input_output_header("Raw Input:".into()))
.child(
div()
.id(("tool-call-raw-input-markdown", entry_ix))
.child(self.render_markdown(
raw_input,
default_markdown_style(
false, false, window, cx,
),
)),
),
)
} else {
this
}
})
.child(self.render_permission_buttons(
tool_call.kind,
options,
@@ -6195,6 +6219,10 @@ impl Render for AcpThreadView {
}
}
fn should_show_tool_call_input(tool_call: &ToolCall) -> bool {
tool_call.source == ToolSource::Mcp
}
fn default_markdown_style(
buffer_font: bool,
muted_text: bool,