[Bugfix][Rust Frontend] Preserve whitespace in GLM string arguments (#59654)

Signed-off-by: ai-jz <ai-jz@users.noreply.github.com> Co-authored-by: ai-jz <ai-jz@users.noreply.github.com> Co-authored-by: Bugen Zhao <i@bugenzhao.com>

ai-jz committed Oct 1, 2026 at 19:10 UTC e5d33a36e71da9e3e122ac9b139f3ab3b944ef71
1 file changed +34 -2
rust/src/parser/src/tool/glm_xml/mod.rs
+34 -2
@@ -261,7 +261,7 @@ fn parse_parameter(input: &mut &str) -> ModalResult<(String, String)> {
261 _: literal(ARG_KEY_END),
262 _: ws0,
263 _: literal(ARG_VALUE_START),
264 - take_until(0.., ARG_VALUE_END).map(str::trim),
264 + take_until(0.., ARG_VALUE_END),
265 _: literal(ARG_VALUE_END),
266 )
267 .parse_next(input)?;
@@ -274,7 +274,7 @@ mod tests {
274 use serde_json::{Value, json};
275 use thiserror_ext::AsReport;
276
277 - use super::Glm45MoeToolParser;
277 + use super::{Glm45MoeToolParser, Glm47MoeToolParser};
278 use crate::tool::test_utils::{collect_stream, split_by_chars, test_tools};
279 use crate::tool::{ToolParser, ToolParserTestExt as _};
280
@@ -320,6 +320,38 @@ mod tests {
320 );
321 }
322
323 + #[test]
324 + fn glm_parse_complete_preserves_string_whitespace() {
325 + let tools = test_tools();
326 + let text = glm45_tool_call("get_weather", &[("city", " Paris \n"), ("days", " 2 ")]);
327 + for mut parser in [
328 + Glm45MoeToolParser::create(&tools).unwrap(),
329 + Glm47MoeToolParser::create(&tools).unwrap(),
330 + ] {
331 + let output = parser.parse_complete(&text).unwrap();
332 + assert_eq!(
333 + serde_json::from_str::<Value>(&output.calls()[0].arguments).unwrap(),
334 + json!({"city": " Paris \n", "days": 2})
335 + );
336 + }
337 + }
338 +
339 + #[test]
340 + fn glm_streaming_preserves_string_whitespace() {
341 + let tools = test_tools();
342 + let text = glm45_tool_call("get_weather", &[("city", " Paris \n"), ("days", " 2 ")]);
343 + for mut parser in [
344 + Glm45MoeToolParser::create(&tools).unwrap(),
345 + Glm47MoeToolParser::create(&tools).unwrap(),
346 + ] {
347 + let output = collect_stream(&mut *parser, &split_by_chars(&text, 7));
348 + assert_eq!(
349 + serde_json::from_str::<Value>(&output.calls()[0].arguments).unwrap(),
350 + json!({"city": " Paris \n", "days": 2})
351 + );
352 + }
353 + }
354 +
355 #[test]
356 fn glm45_streaming_extracts_multiple_tool_calls() {
357 let mut parser = Glm45MoeToolParser::new(&test_tools());