Harden local tool execution

This commit is contained in:
Georg Bauer
2026-07-26 14:36:38 +02:00
parent 171b041ba6
commit c9f0c3661c
7 changed files with 1018 additions and 141 deletions

View File

@@ -75,6 +75,17 @@ impl App {
if message.system {
continue;
}
if message.tool
&& index > 0
&& !crate::agent::stored_tool_cards(
self.config.model,
&self.conversation[index - 1].content,
None,
)
.is_empty()
{
continue;
}
let label = if message.user {
"You"
} else if message.tool {
@@ -135,11 +146,39 @@ impl App {
text(self.activity.as_deref().unwrap_or("Loading model…")).size(14),
);
}
if !message.user && !message.tool {
for summary in
crate::agent::tool_summaries(self.config.model, &message.content)
{
body = body.push(text(summary).size(13).color(muted_text()));
if !message.user {
let stored_result = self
.conversation
.get(index + 1)
.filter(|message| message.tool)
.map(|message| message.content.as_str());
let cards = if stored_result.is_none() && {
#[cfg(target_os = "macos")]
{
!self.tool_cards.is_empty()
}
#[cfg(not(target_os = "macos"))]
{
false
}
} {
#[cfg(target_os = "macos")]
{
self.tool_cards.clone()
}
#[cfg(not(target_os = "macos"))]
{
Vec::new()
}
} else {
crate::agent::stored_tool_cards(
self.config.model,
&message.content,
stored_result,
)
};
if !cards.is_empty() {
body = body.push(tool_cards(cards));
}
}
let user = message.user;
@@ -295,3 +334,80 @@ impl App {
.into()
}
}
fn tool_cards(cards: Vec<crate::agent::ToolCard>) -> Element<'static, Message> {
let mut rows = column![].spacing(0);
for (index, card) in cards.into_iter().enumerate() {
if index > 0 {
rows = rows.push(horizontal_rule(1));
}
let parameters = crate::agent::tool_parameters(&card.call);
let call = crate::agent::tool_call_text(&card.call);
let copy_call = tooltip(
action_button(text("Copy call").size(11))
.padding([5, 9])
.on_press(Message::CopyToolText(call)),
container(text("Copy tool name and all arguments").size(11))
.padding(8)
.style(preference_group_style),
tooltip::Position::Top,
)
.gap(6);
let copy_result = action_button(text("Copy result").size(11)).padding([5, 9]);
let copy_result = if let Some(result) = &card.result {
copy_result.on_press(Message::CopyToolText(result.clone()))
} else {
copy_result
};
let copy_result = tooltip(
copy_result,
container(text("Copy the complete tool result").size(11))
.padding(8)
.style(preference_group_style),
tooltip::Position::Top,
)
.gap(6);
let mut actions = row![copy_call, copy_result]
.spacing(6)
.align_y(Alignment::Center);
if let Some(path) = card
.result
.as_deref()
.and_then(crate::agent::tool_output_path)
{
actions = actions.push(
tooltip(
action_button(text("Open output").size(11))
.padding([5, 9])
.on_press(Message::OpenToolOutput(path)),
container(text("Open the complete output file").size(11))
.padding(8)
.style(preference_group_style),
tooltip::Position::Top,
)
.gap(6),
);
}
let mut content = column![
row![
text(card.call.name).size(13),
Space::with_width(Length::Fill),
text(card.state.label()).size(11).color(muted_text()),
actions,
]
.spacing(8)
.align_y(Alignment::Center),
text(parameters).size(12).color(muted_text()),
]
.spacing(5);
if let Some(result) = card.result {
let bounded = crate::agent::bounded_tool_text(&result, 1_200);
content = content.push(text(bounded).size(12));
}
rows = rows.push(container(content).padding(10).width(Length::Fill));
}
container(rows)
.width(Length::Fill)
.style(preference_group_style)
.into()
}