From e8c0d3df52a25f53dc3d649f3827163973a3816f Mon Sep 17 00:00:00 2001 From: "ilblackdragon@gmail.com" Date: Sun, 22 Mar 2026 23:08:52 -0700 Subject: [PATCH] fix(bridge): parse JSON tool output to prevent double-serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From trace analysis: web_search returned a JSON string, which was wrapped as serde_json::json!(string) creating a Value::String containing JSON. When Monty got this as MontyObject::String, the Python code couldn't index it with result['title'] → TypeError. Fix: try parsing the tool output string as JSON first. If valid, use the parsed Value (becomes a Python dict/list). If not valid JSON, keep as string. This means web_search results are directly indexable in Python: results = web_search(query="...") print(results["results"][0]["title"]) # works now Co-Authored-By: Claude Opus 4.6 (1M context) --- src/bridge/effect_adapter.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/bridge/effect_adapter.rs b/src/bridge/effect_adapter.rs index f83bc45e..281b8102 100644 --- a/src/bridge/effect_adapter.rs +++ b/src/bridge/effect_adapter.rs @@ -60,13 +60,21 @@ impl EffectExecutor for EffectBridgeAdapter { .await; match result { - Ok(output) => Ok(ActionResult { - call_id: String::new(), // Caller fills this in - action_name: action_name.to_string(), - output: serde_json::json!(output), - is_error: false, - duration: Duration::from_millis(1), // TODO: measure actual duration - }), + Ok(output) => { + // Tool output is a String. If it's valid JSON, parse it so the + // Python code gets a dict/list instead of a string that needs + // manual parsing. This prevents double-serialization. + let output_value = serde_json::from_str::(&output) + .unwrap_or(serde_json::Value::String(output)); + + Ok(ActionResult { + call_id: String::new(), // Caller fills this in + action_name: action_name.to_string(), + output: output_value, + is_error: false, + duration: Duration::from_millis(1), // TODO: measure actual duration + }) + } Err(e) => Ok(ActionResult { call_id: String::new(), action_name: action_name.to_string(),