From fc8a0aa6ae945dc96065f99a50ece76b9145be79 Mon Sep 17 00:00:00 2001 From: ooodc <549496103@qq.com> Date: Mon, 27 Apr 2026 09:29:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0normalize=5Ftool=5Far?= =?UTF-8?q?guments=E5=87=BD=E6=95=B0=E4=BB=A5=E8=A7=A3=E6=9E=90=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E4=B8=B2=E5=8C=96=E7=9A=84JSON=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E6=95=B0=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agent/agent_loop.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/agent/agent_loop.rs b/src/agent/agent_loop.rs index a6da683..8c08e09 100644 --- a/src/agent/agent_loop.rs +++ b/src/agent/agent_loop.rs @@ -125,6 +125,15 @@ fn parse_pending_tool_output(output: &str) -> Option { output.strip_prefix(PENDING_USER_ACTION_MARKER).map(|rest| rest.trim().to_string()) } +fn normalize_tool_arguments(arguments: &serde_json::Value) -> serde_json::Value { + match arguments { + serde_json::Value::String(raw) => { + serde_json::from_str(raw).unwrap_or_else(|_| arguments.clone()) + } + _ => arguments.clone(), + } +} + fn is_recoverable_llm_error(error: &str) -> bool { let normalized = error.to_ascii_lowercase(); normalized.contains("504") @@ -720,8 +729,10 @@ impl AgentLoop { /// Internal tool execution without event tracking. async fn execute_tool_internal(&self, tool_call: &ToolCall) -> ToolExecutionOutcome { + let normalized_arguments = normalize_tool_arguments(&tool_call.arguments); + if tool_call.name == "skill_activate" { - let skill_name = match tool_call.arguments.get("name").and_then(|v| v.as_str()) { + let skill_name = match normalized_arguments.get("name").and_then(|v| v.as_str()) { Some(name) if !name.trim().is_empty() => name, _ => { self.record_skill_event( @@ -729,7 +740,7 @@ impl AgentLoop { None, serde_json::json!({ "reason": "missing_name", - "arguments": tool_call.arguments, + "arguments": normalized_arguments, }), ); return ToolExecutionOutcome::failure( @@ -752,7 +763,7 @@ impl AgentLoop { Some(skill_name), serde_json::json!({ "reason": err, - "arguments": tool_call.arguments, + "arguments": normalized_arguments, }), ); ToolExecutionOutcome::failure( @@ -774,7 +785,7 @@ impl AgentLoop { } }; - match tool.execute_with_context(&self.tool_context, tool_call.arguments.clone()).await { + match tool.execute_with_context(&self.tool_context, normalized_arguments).await { Ok(result) => { if result.success { if let Some(pending_output) = parse_pending_tool_output(&result.output) { @@ -940,6 +951,22 @@ mod tests { assert!(parse_pending_tool_output("normal output").is_none()); } + #[test] + fn test_normalize_tool_arguments_parses_stringified_json() { + let normalized = normalize_tool_arguments(&serde_json::Value::String( + "{\"command\":\"ls -la\"}".to_string(), + )); + + assert_eq!(normalized, serde_json::json!({ "command": "ls -la" })); + } + + #[test] + fn test_normalize_tool_arguments_keeps_plain_string() { + let normalized = normalize_tool_arguments(&serde_json::Value::String("plain text".to_string())); + + assert_eq!(normalized, serde_json::Value::String("plain text".to_string())); + } + #[test] fn test_build_content_blocks_skips_non_image_media_refs() { let temp_dir = tempdir().unwrap();