Clarify malformed tool call boundaries
Co-Authored-By: siGit Code <noreply@sigit.si>
Seto Elkahfi committed
Sep 13, 2026 at 15:39 UTC
90e69e7d7f94e2cd2332b5a0961a751c81830cc2
1 file changed
+14
src/inline_tool_calls.rs
+14
@@ -100,6 +100,12 @@ fn parse_xml_block(inner: &str, tools: &[ToolSpec]) -> Option<Recovered> {
100
let mut args = serde_json::Map::new();
101
let mut first_key = true;
102
while !rest.is_empty() {
103
+ // The GLM alias is permitted exactly once, at the first key. A later
104
+ // `<tool_call>` must reject the complete block rather than letting a
105
+ // partial call escape recovery.
106
+ if !first_key && rest.starts_with(XML_OPEN_TAG) {
107
+ return None;
108
+ }
109
let key_open = if first_key && malformed_first_key {
110
XML_OPEN_TAG
111
} else {
@@ -559,6 +565,14 @@ mod tests {
565
assert_eq!(args["cwd"], "/tmp");
566
}
567
568
+ #[test]
569
+ fn unknown_glm_tool_is_left_as_text() {
570
+ let text = "<tool_call>not_offered<tool_call>command</arg_key><arg_value>pwd</arg_value></tool_call>";
571
+ let (out, calls) = extract(text, &[run_command_spec()]);
572
+ assert_eq!(out, text);
573
+ assert!(calls.is_empty());
574
+ }
575
+
576
#[test]
577
fn malformed_glm_marker_is_accepted_only_for_the_first_argument() {
578
let text = "<tool_call>run_command<arg_key>command</arg_key><arg_value>pwd</arg_value><tool_call>cwd</arg_key><arg_value>/tmp</arg_value></tool_call>";