Handles pre-execution guards that feed an immediate tool result back to the LLM. Returns `true` when the tool call has been fully handled and should not continue through safety gating or execution.
(
&self,
tool_call: &ToolCall,
state: &mut ExecutionLoopState,
event_tx: &Option<mpsc::Sender<AgentEvent>>,
)
| 9 | /// Returns `true` when the tool call has been fully handled and should not continue |
| 10 | /// through safety gating or execution. |
| 11 | pub(super) async fn handle_tool_preflight_guard( |
| 12 | &self, |
| 13 | tool_call: &ToolCall, |
| 14 | state: &mut ExecutionLoopState, |
| 15 | event_tx: &Option<mpsc::Sender<AgentEvent>>, |
| 16 | ) -> anyhow::Result<bool> { |
| 17 | if let Some((duplicate_count, error_msg)) = state.duplicate_tool_call( |
| 18 | &tool_call.name, |
| 19 | &tool_call.args, |
| 20 | self.config.duplicate_tool_call_threshold, |
| 21 | ) { |
| 22 | tracing::warn!( |
| 23 | tool_name = tool_call.name.as_str(), |
| 24 | duplicate_count = duplicate_count, |
| 25 | threshold = self.config.duplicate_tool_call_threshold, |
| 26 | "Duplicate tool call threshold exceeded" |
| 27 | ); |
| 28 | |
| 29 | if let Some(tx) = event_tx { |
| 30 | tx.send(AgentEvent::Error { |
| 31 | message: error_msg.clone(), |
| 32 | }) |
| 33 | .await |
| 34 | .ok(); |
| 35 | } |
| 36 | |
| 37 | state |
| 38 | .messages |
| 39 | .push(Message::tool_result(&tool_call.id, &error_msg, true)); |
| 40 | return Ok(true); |
| 41 | } |
| 42 | |
| 43 | if let Some(parse_error) = tool_call.args.get("__parse_error").and_then(|v| v.as_str()) { |
| 44 | let parse_outcome = |
| 45 | state.record_parse_error(parse_error, self.config.max_parse_retries); |
| 46 | tracing::warn!( |
| 47 | tool = tool_call.name.as_str(), |
| 48 | parse_error_count = parse_outcome.count, |
| 49 | max_parse_retries = self.config.max_parse_retries, |
| 50 | "Malformed tool arguments from LLM" |
| 51 | ); |
| 52 | |
| 53 | if let Some(tx) = event_tx { |
| 54 | tx.send(AgentEvent::ToolEnd { |
| 55 | id: tool_call.id.clone(), |
| 56 | name: tool_call.name.clone(), |
| 57 | output: parse_outcome.output.clone(), |
| 58 | exit_code: 1, |
| 59 | metadata: None, |
| 60 | error_kind: None, |
| 61 | }) |
| 62 | .await |
| 63 | .ok(); |
| 64 | } |
| 65 | |
| 66 | state.messages.push(Message::tool_result( |
| 67 | &tool_call.id, |
| 68 | &parse_outcome.output, |
no test coverage detected