1use async_trait::async_trait;
2use openai_protocol::common::Tool;
3use serde_json::{json, Map, Value};
4
5use crate::{
6 errors::ParserResult,
7 factory::GrammarStart,
8 parsers::helpers,
9 traits::ToolParser,
10 types::{FunctionCall, StreamingParseResult, ToolCall, ToolCallItem},
11};
12
13const NAMESPACE: &str = "]<]minimax[>[";
15const TOOL_CALL_START: &str = "]<]minimax[>[<tool_call>";
17const TOOL_CALL_END: &str = "]<]minimax[>[</tool_call>";
19const INVOKE_START: &str = "]<]minimax[>[<invoke";
21const INVOKE_END: &str = "]<]minimax[>[</invoke>";
23const ELEMENT_START: &str = "]<]minimax[>[<";
25const ELEMENT_END_START: &str = "]<]minimax[>[</";
27const MIXED_TEXT_FIELD: &str = "$text";
29const THINK_START: &str = "<mm:think>";
31const THINK_END: &str = "</mm:think>";
33const ITEM: &str = "item";
35const CALL_EXITS: [&str; 3] = ["</invoke>", "<invoke name=\"", "</tool_call>"];
37const DEPTH: usize = 16;
39
40pub struct MinimaxM3Parser {
63 buffer: String,
65 prev_tool_call_arr: Vec<Value>,
66 current_tool_id: i32,
67 streamed_args_for_tool: Vec<String>,
68 in_tool_call: bool,
69}
70
71enum ParamValue {
73 Text(String),
74 Elements(Vec<(String, ParamValue)>),
75}
76
77impl MinimaxM3Parser {
78 pub fn new() -> Self {
80 Self {
81 buffer: String::new(),
82 prev_tool_call_arr: Vec::new(),
83 current_tool_id: -1,
84 streamed_args_for_tool: Vec::new(),
85 in_tool_call: false,
86 }
87 }
88
89 fn coerce_leaf(text: &str, declared_type: Option<&str>) -> Value {
92 if text.trim().is_empty() {
97 match declared_type {
98 Some("array") => return Value::Array(Vec::new()),
99 Some("object") => return Value::Object(Map::new()),
100 _ => {}
101 }
102 }
103
104 if let Some(value) = helpers::coerce_by_schema_type(text, declared_type) {
105 return value;
106 }
107 Self::infer_value(text)
108 }
109
110 fn infer_value(text: &str) -> Value {
112 match text {
113 "true" | "True" => return Value::Bool(true),
114 "false" | "False" => return Value::Bool(false),
115 "null" | "None" => return Value::Null,
116 _ => {}
117 }
118
119 if let Ok(num) = text.parse::<i64>() {
120 return Value::Number(num.into());
121 }
122 if let Ok(num) = text.parse::<f64>() {
123 if let Some(n) = serde_json::Number::from_f64(num) {
124 return Value::Number(n);
125 }
126 }
127
128 Value::String(text.to_string())
129 }
130
131 fn longest_partial_suffix(buffer: &str, token: &str) -> Option<usize> {
138 if buffer.is_empty() || token.is_empty() {
139 return None;
140 }
141 token
142 .char_indices()
143 .skip(1)
144 .map(|(i, _)| i)
145 .filter(|&i| buffer.ends_with(&token[..i]))
146 .max()
147 }
148
149 fn could_start_invoke(buffer: &str) -> bool {
155 let Some(after_wrapper) = buffer.strip_prefix(TOOL_CALL_START) else {
156 return false;
157 };
158 let candidate = after_wrapper.trim_start();
159 if candidate.is_empty() || INVOKE_START.starts_with(candidate) {
160 return true;
161 }
162
163 let Some(after_invoke) = candidate.strip_prefix(INVOKE_START) else {
164 return false;
165 };
166 after_invoke.is_empty()
167 || after_invoke
168 .chars()
169 .next()
170 .is_some_and(|c| c.is_whitespace() || c == '>')
171 }
172
173 fn decode_xml_entities(text: &str) -> String {
175 text.replace("<", "<")
176 .replace(">", ">")
177 .replace(""", "\"")
178 .replace("'", "'")
179 .replace("&", "&")
180 }
181
182 fn parse_invoke_name(header: &str) -> Option<String> {
185 let idx = header.find("name")?;
186 let after = &header[idx + "name".len()..];
187 let after = after.trim_start();
188 let after = after.strip_prefix('=')?.trim_start();
189 if let Some(rest) = after.strip_prefix('"') {
190 let end = rest.find('"')?;
191 Some(rest[..end].trim().to_string())
192 } else if let Some(rest) = after.strip_prefix('\'') {
193 let end = rest.find('\'')?;
194 Some(rest[..end].trim().to_string())
195 } else {
196 let end = after
197 .find(|c: char| c.is_whitespace())
198 .unwrap_or(after.len());
199 let name = after[..end].trim();
200 if name.is_empty() {
201 None
202 } else {
203 Some(name.to_string())
204 }
205 }
206 }
207
208 fn parse_element_body(
213 input: &str,
214 name: &str,
215 schema: Option<&Value>,
216 parent_name: Option<&str>,
217 ) -> Option<(ParamValue, usize)> {
218 let close_tag = format!("{ELEMENT_END_START}{name}>");
219 let mut pos = 0;
220 let mut text = String::new();
221 let mut children: Vec<(String, ParamValue)> = Vec::new();
222
223 loop {
224 let rest = &input[pos..];
227 let text_chunk = &rest[..rest.find(NAMESPACE)?];
228 text.push_str(text_chunk);
229 pos += text_chunk.len();
230
231 let rest = &input[pos..];
232 if rest.starts_with(&close_tag) {
233 pos += close_tag.len();
234 break;
235 }
236 if text.trim().is_empty()
237 && children.is_empty()
238 && matches!(Self::schema_type(schema), Some("array" | "object"))
239 && parent_name.is_some_and(|parent| {
240 rest.starts_with(&format!("{ELEMENT_END_START}{parent}>"))
241 })
242 {
243 break;
246 }
247 if rest.starts_with(ELEMENT_START) {
248 let (child_name, child_value, consumed) =
250 Self::parse_element(rest, schema, Some(name))?;
251 children.push((child_name, child_value));
252 pos += consumed;
253 continue;
254 }
255 return None;
258 }
259
260 let value = if children.is_empty() {
261 ParamValue::Text(text)
262 } else {
263 if !text.trim().is_empty() {
264 Self::push_mixed_text(&mut children, text);
265 }
266 ParamValue::Elements(children)
267 };
268 Some((value, pos))
269 }
270
271 fn parse_element(
274 input: &str,
275 parent_schema: Option<&Value>,
276 parent_name: Option<&str>,
277 ) -> Option<(String, ParamValue, usize)> {
278 let after_start = input.strip_prefix(ELEMENT_START)?;
279 let gt = after_start.find('>')?;
280 let name = after_start[..gt].trim().to_string();
281 if name.is_empty() || name.starts_with('/') {
282 return None;
283 }
284 let body_start = ELEMENT_START.len() + gt + 1;
285 let parent_schema = Self::container_schema(parent_schema).or(parent_schema);
286 let schema = if Self::schema_type(parent_schema) == Some("array") {
287 parent_schema.and_then(|schema| schema.get("items"))
288 } else {
289 Self::property_schema(parent_schema, &name)
290 };
291 let schema = Self::container_schema(schema).or(schema);
292 let (value, body_consumed) =
293 Self::parse_element_body(&input[body_start..], &name, schema, parent_name)?;
294 Some((name, value, body_start + body_consumed))
295 }
296
297 fn push_mixed_text(children: &mut Vec<(String, ParamValue)>, text: String) {
300 let mut field = MIXED_TEXT_FIELD.to_string();
301 while children.iter().any(|(name, _)| *name == field) {
302 field.insert(0, '$');
303 }
304 children.push((field, ParamValue::Text(text)));
305 }
306
307 fn schema_type(schema: Option<&Value>) -> Option<&str> {
309 schema?.get("type")?.as_str()
310 }
311
312 fn container_schema(schema: Option<&Value>) -> Option<&Value> {
317 let schema = schema?;
318 if schema.get("type").is_some() {
319 return matches!(Self::schema_type(Some(schema)), Some("array" | "object"))
320 .then_some(schema);
321 }
322
323 let mut compositions = ["oneOf", "anyOf", "allOf"]
324 .into_iter()
325 .filter_map(|key| schema.get(key).map(|branches| (key, branches)));
326 let (keyword, branches) = compositions.next()?;
327 if compositions.next().is_some() {
328 return None;
329 }
330 if schema.as_object()?.keys().any(|key| {
332 key != keyword
333 && !matches!(
334 key.as_str(),
335 "title"
336 | "description"
337 | "default"
338 | "examples"
339 | "deprecated"
340 | "readOnly"
341 | "writeOnly"
342 | "$comment"
343 )
344 }) {
345 return None;
346 }
347 let branches = branches.as_array()?;
348 if keyword == "allOf" && branches.len() != 1 {
349 return None;
350 }
351 let mut container = None;
352 for branch in branches {
353 if keyword != "allOf" && Self::schema_type(Some(branch)) == Some("null") {
354 continue;
355 }
356 let resolved = Self::container_schema(Some(branch))?;
357 if container.replace(resolved).is_some() {
358 return None;
359 }
360 }
361 container
362 }
363
364 fn property_schema<'a>(schema: Option<&'a Value>, name: &str) -> Option<&'a Value> {
366 let schema = schema?;
367 if let Some(property) = schema.get("properties").and_then(|value| value.get(name)) {
368 return Some(property);
369 }
370
371 for keyword in ["oneOf", "anyOf", "allOf"] {
372 for branch in schema
373 .get(keyword)
374 .and_then(Value::as_array)
375 .into_iter()
376 .flatten()
377 {
378 if let Some(property) = Self::property_schema(Some(branch), name) {
379 return Some(property);
380 }
381 }
382 }
383
384 None
385 }
386
387 fn value_to_json(value: ParamValue, schema: Option<&Value>) -> Value {
391 let schema = Self::container_schema(schema).or(schema);
392 match value {
393 ParamValue::Text(text) => {
394 let decoded = Self::decode_xml_entities(&text);
395 Self::coerce_leaf(&decoded, Self::schema_type(schema))
396 }
397 ParamValue::Elements(children) => {
398 if Self::schema_type(schema) == Some("array") {
401 let items_schema = schema.and_then(|s| s.get("items"));
402 let items = children
403 .into_iter()
404 .map(|(_, v)| Self::value_to_json(v, items_schema))
405 .collect();
406 return Value::Array(items);
407 }
408
409 let mut map: Map<String, Value> = Map::new();
410 for (name, child) in children {
411 let child_schema = Self::property_schema(schema, &name);
412 let child_json = Self::value_to_json(child, child_schema);
413 match map.get_mut(&name) {
414 Some(Value::Array(arr)) => arr.push(child_json),
415 Some(existing) => {
416 let prev = existing.take();
417 *existing = Value::Array(vec![prev, child_json]);
418 }
419 None => {
420 map.insert(name, child_json);
421 }
422 }
423 }
424 Value::Object(map)
425 }
426 }
427 }
428
429 fn parse_invoke_params(body: &str, params_schema: Option<&Value>) -> Option<Value> {
435 let mut map: Map<String, Value> = Map::new();
436 let mut pos = 0;
437
438 loop {
439 let rest = &body[pos..];
440 let trimmed = rest.trim_start();
441 let trim_len = rest.len() - trimmed.len();
442 if trimmed.is_empty() {
443 break;
444 }
445 if !trimmed.starts_with(ELEMENT_START) {
446 break;
448 }
449 pos += trim_len;
450 let (name, value, consumed) = Self::parse_element(&body[pos..], params_schema, None)?;
451 pos += consumed;
452 let json = Self::value_to_json(value, Self::property_schema(params_schema, &name));
453 match map.get_mut(&name) {
454 Some(Value::Array(arr)) => arr.push(json),
455 Some(existing) => {
456 let prev = existing.take();
457 *existing = Value::Array(vec![prev, json]);
458 }
459 None => {
460 map.insert(name, json);
461 }
462 }
463 }
464
465 Some(Value::Object(map))
466 }
467
468 fn parse_invoke(block: &str, tools: &[Tool]) -> Option<ToolCall> {
471 let after_invoke = block.strip_prefix(INVOKE_START)?;
473 let gt = after_invoke.find('>')?;
474 let header = &after_invoke[..gt];
475 let name = Self::parse_invoke_name(header)?;
476 let body = &after_invoke[gt + 1..];
477
478 let params_schema = tools
479 .iter()
480 .find(|t| t.function.name == name)
481 .map(|t| &t.function.parameters);
482 let arguments = Self::parse_invoke_params(body, params_schema)?;
483 let arguments_str = serde_json::to_string(&arguments).ok()?;
484
485 Some(ToolCall {
486 function: FunctionCall {
487 name,
488 arguments: arguments_str,
489 },
490 })
491 }
492
493 fn parse_tool_calls(text: &str, tools: &[Tool]) -> (Vec<ToolCall>, Option<usize>) {
496 let mut calls = Vec::new();
497 let mut first_pos = None;
498 let mut search_from = 0;
499
500 while let Some(rel_start) = text[search_from..].find(TOOL_CALL_START) {
501 let block_start = search_from + rel_start;
502 let inner_start = block_start + TOOL_CALL_START.len();
503 let Some(rel_end) = text[inner_start..].find(TOOL_CALL_END) else {
504 break;
505 };
506 let inner_end = inner_start + rel_end;
507 let inner = &text[inner_start..inner_end];
508
509 let mut invoke_from = 0;
511 while let Some(rel_inv) = inner[invoke_from..].find(INVOKE_START) {
512 let inv_start = invoke_from + rel_inv;
513 let Some(rel_inv_end) = inner[inv_start..].find(INVOKE_END) else {
514 break;
515 };
516 let inv_end = inv_start + rel_inv_end;
517 let inv_block = &inner[inv_start..inv_end];
518 if let Some(call) = Self::parse_invoke(inv_block, tools) {
519 if first_pos.is_none() {
520 first_pos = Some(block_start);
521 }
522 calls.push(call);
523 }
524 invoke_from = inv_end + INVOKE_END.len();
525 }
526
527 search_from = inner_end + TOOL_CALL_END.len();
528 }
529
530 (calls, first_pos)
531 }
532
533 fn parse_complete_inner(text: &str, tools: &[Tool]) -> (String, Vec<ToolCall>) {
535 if !text.contains(TOOL_CALL_START) {
536 return (text.to_string(), vec![]);
537 }
538 let (calls, first_pos) = Self::parse_tool_calls(text, tools);
539 if calls.is_empty() {
540 return (text.to_string(), vec![]);
541 }
542 let normal_text = match first_pos {
543 Some(pos) => text[..pos].to_string(),
544 None => text.to_string(),
545 };
546 (normal_text, calls)
547 }
548
549 pub fn build_structural_tag(tools: &[Tool], at_least_one: bool) -> Value {
572 let invokes: Vec<Value> = tools
573 .iter()
574 .filter(|tool| !tool.function.name.is_empty())
575 .map(|tool| Elements::of(&tool.function.parameters).invoke(&tool.function.name))
576 .collect();
577 let invokes = if invokes.is_empty() {
580 json!({ "type": "const_string", "value": "" })
581 } else {
582 json!({
583 "type": "tags_with_separator",
584 "tags": invokes,
585 "separator": "",
586 "at_least_one": true,
587 })
588 };
589 json!({
590 "format": {
591 "type": "sequence",
592 "elements": [
593 Self::before_the_block(),
594 {
595 "type": "triggered_tags",
596 "triggers": [TOOL_CALL_START],
597 "tags": [{
598 "type": "tag",
599 "begin": format!("{TOOL_CALL_START}\n"),
600 "content": invokes,
601 "end": TOOL_CALL_END,
602 }],
603 "at_least_one": at_least_one,
604 },
605 ],
606 }
607 })
608 }
609
610 fn before_the_block() -> Value {
615 json!({
616 "type": "optional",
617 "content": {
618 "type": "or",
619 "elements": [
620 {
621 "type": "tag",
622 "begin": THINK_START,
623 "content": {
624 "type": "any_text",
625 "excludes": [THINK_START, THINK_END, NAMESPACE],
626 },
627 "end": THINK_END,
628 },
629 { "type": "const_string", "value": THINK_END },
630 ],
631 },
632 })
633 }
634
635 pub fn reasoning_prefix(start: GrammarStart) -> Option<Value> {
647 match start {
648 GrammarStart::AfterReasoning => None,
649 GrammarStart::Unknown => Some(json!({
650 "type": "any_text",
651 "excludes": [NAMESPACE],
652 })),
653 GrammarStart::FirstToken => Some(json!({
654 "type": "tag",
655 "begin": "",
656 "content": {
657 "type": "any_text",
658 "excludes": [THINK_START, THINK_END, NAMESPACE],
659 },
660 "end": THINK_END,
661 })),
662 }
663 }
664}
665
666struct Elements<'a> {
678 root: &'a Value,
679 path: Vec<&'a str>,
681}
682
683impl<'a> Elements<'a> {
684 fn of(root: &'a Value) -> Self {
685 Self {
686 root,
687 path: Vec::new(),
688 }
689 }
690
691 fn invoke(mut self, name: &str) -> Value {
694 let content = match self.follow(self.root) {
695 Some((schema, pushed)) => {
696 let content = self.members(schema, "</invoke>", DEPTH);
697 self.unwind(pushed);
698 content
699 }
700 None => Self::free_text("</invoke>"),
701 };
702 json!({
703 "type": "tag",
704 "begin": format!("{INVOKE_START} name=\"{name}\">"),
705 "content": content,
706 "end": format!("{INVOKE_END}\n"),
707 })
708 }
709
710 fn definition(&self, pointer: &str) -> Option<&'a Value> {
714 let mut segments = pointer.strip_prefix("#/")?.split('/');
715 let block = match segments.next()? {
716 block @ ("$defs" | "definitions") => block,
717 _ => return None,
718 };
719 let mut node = self.root.get(block)?;
720 for segment in segments {
721 node = node.get(segment.replace("~1", "/").replace("~0", "~"))?;
722 }
723 Some(node)
724 }
725
726 fn follow(&mut self, schema: &'a Value) -> Option<(&'a Value, usize)> {
731 let mut schema = schema;
732 let mut pushed = 0;
733 while let Some(pointer) = schema.get("$ref").and_then(Value::as_str) {
734 let Some(target) = self.definition(pointer) else {
735 break;
736 };
737 if self.path.contains(&pointer) || pushed == DEPTH {
738 self.unwind(pushed);
739 return None;
740 }
741 self.path.push(pointer);
742 pushed += 1;
743 schema = target;
744 }
745 Some((schema, pushed))
746 }
747
748 fn unwind(&mut self, pushed: usize) {
750 let keep = self.path.len().saturating_sub(pushed);
751 self.path.truncate(keep);
752 }
753
754 fn members(&mut self, schema: &'a Value, close: &str, depth: usize) -> Value {
758 let properties = schema
759 .get("properties")
760 .and_then(Value::as_object)
761 .filter(|properties| !properties.is_empty());
762 let Some(properties) = properties else {
763 return Self::free_text(close);
764 };
765 let required: Vec<&str> = schema
766 .get("required")
767 .and_then(Value::as_array)
768 .map(|names| names.iter().filter_map(Value::as_str).collect())
769 .unwrap_or_default();
770 let elements: Vec<Value> = properties
771 .iter()
772 .filter_map(|(key, schema)| {
773 let element = self.element(key, schema, depth)?;
774 Some(if required.contains(&key.as_str()) {
775 element
776 } else {
777 json!({ "type": "optional", "content": element })
778 })
779 })
780 .collect();
781 if elements.is_empty() {
782 return json!({ "type": "const_string", "value": "" });
783 }
784 json!({ "type": "sequence", "elements": elements })
785 }
786
787 fn element(&mut self, key: &str, schema: &'a Value, depth: usize) -> Option<Value> {
790 let value = self.value(schema, &format!("</{key}>"), depth)?;
791 Some(json!({
792 "type": "sequence",
793 "elements": [
794 { "type": "const_string", "value": format!("{ELEMENT_START}{key}>") },
795 value,
796 { "type": "const_string", "value": format!("{ELEMENT_END_START}{key}>") },
797 ],
798 }))
799 }
800
801 fn value(&mut self, schema: &'a Value, close: &str, depth: usize) -> Option<Value> {
804 let Some(depth) = depth.checked_sub(1) else {
805 return Some(Self::free_text(close));
806 };
807 let Some((schema, pushed)) = self.follow(schema) else {
808 return Some(Self::free_text(close));
809 };
810 let value = match MinimaxM3Parser::schema_type(Some(schema)) {
811 Some("null") => None,
812 Some("boolean") => Some(Self::one_of(["true", "false", "True", "False"])),
813 Some("object") => Some(self.members(schema, close, depth)),
814 Some("array") => Some(match schema.get("items") {
815 Some(items) => self.items(items, depth),
816 None => Self::free_text(close),
817 }),
818 Some("string") => Some(match schema.get("enum").and_then(Value::as_array) {
819 Some(values) if !values.is_empty() && values.iter().all(Value::is_string) => {
820 Self::one_of(values.iter().filter_map(Value::as_str))
821 }
822 _ => Self::text(close),
823 }),
824 Some("integer" | "number") => Some(self.fragment(schema)),
825 _ => Some(Self::free_text(close)),
826 };
827 self.unwind(pushed);
828 value
829 }
830
831 fn items(&mut self, items: &'a Value, depth: usize) -> Value {
834 let open = format!("{ELEMENT_START}{ITEM}>");
835 let close = format!("{ELEMENT_END_START}{ITEM}>");
836 let item = match self.value(items, &format!("</{ITEM}>"), depth) {
837 Some(value) => json!({
838 "type": "sequence",
839 "elements": [
840 { "type": "const_string", "value": open },
841 value,
842 { "type": "const_string", "value": close },
843 ],
844 }),
845 None => json!({ "type": "const_string", "value": format!("{open}{close}") }),
846 };
847 json!({ "type": "star", "content": item })
848 }
849
850 fn fragment(&self, schema: &'a Value) -> Value {
854 let mut fragment = schema.clone();
855 if Self::holds_reference(schema) {
856 if let Some(object) = fragment.as_object_mut() {
857 for name in ["$defs", "definitions"] {
858 if let Some(block) = self.root.get(name) {
859 object
860 .entry(name.to_string())
861 .or_insert_with(|| block.clone());
862 }
863 }
864 }
865 }
866 json!({ "type": "json_schema", "json_schema": fragment })
867 }
868
869 fn holds_reference(schema: &Value) -> bool {
871 match schema {
872 Value::Object(object) => {
873 object.contains_key("$ref") || object.values().any(Self::holds_reference)
874 }
875 Value::Array(items) => items.iter().any(Self::holds_reference),
876 _ => false,
877 }
878 }
879
880 fn one_of(words: impl IntoIterator<Item = &'a str>) -> Value {
882 let words: Vec<Value> = words
883 .into_iter()
884 .map(|word| json!({ "type": "const_string", "value": word }))
885 .collect();
886 json!({ "type": "or", "elements": words })
887 }
888
889 fn text(close: &str) -> Value {
892 Self::excluding(&[NAMESPACE, close])
893 }
894
895 fn free_text(close: &str) -> Value {
899 Self::excluding(&[close])
900 }
901
902 fn excluding(first: &[&str]) -> Value {
905 let mut excludes: Vec<&str> = Vec::new();
906 for text in first.iter().chain(CALL_EXITS.iter()) {
907 if !excludes.contains(text) {
908 excludes.push(text);
909 }
910 }
911 json!({ "type": "any_text", "excludes": excludes })
912 }
913}
914
915impl Default for MinimaxM3Parser {
916 fn default() -> Self {
917 Self::new()
918 }
919}
920
921#[async_trait]
922impl ToolParser for MinimaxM3Parser {
923 async fn parse_complete(&self, text: &str) -> ParserResult<(String, Vec<ToolCall>)> {
924 Ok(Self::parse_complete_inner(text, &[]))
925 }
926
927 async fn parse_complete_with_tools(
928 &self,
929 text: &str,
930 tools: &[Tool],
931 ) -> ParserResult<(String, Vec<ToolCall>)> {
932 Ok(Self::parse_complete_inner(text, tools))
933 }
934
935 async fn parse_incremental(
936 &mut self,
937 chunk: &str,
938 tools: &[Tool],
939 ) -> ParserResult<StreamingParseResult> {
940 self.buffer.push_str(chunk);
941 let mut normal_text = String::new();
942 let mut calls = Vec::new();
943
944 loop {
945 if !self.in_tool_call {
947 if let Some(start) = self.buffer.find(TOOL_CALL_START) {
948 normal_text.push_str(&self.buffer[..start]);
949 self.buffer = self.buffer[start..].to_string();
950 self.in_tool_call = true;
951 continue;
952 }
953
954 if let Some(partial_len) =
956 Self::longest_partial_suffix(&self.buffer, TOOL_CALL_START)
957 {
958 let end = self.buffer.len() - partial_len;
959 normal_text.push_str(&self.buffer[..end]);
960 self.buffer = self.buffer[end..].to_string();
961 } else {
962 normal_text.push_str(&self.buffer);
963 self.buffer.clear();
964 }
965 break;
966 }
967
968 let Some(end_rel) = self.buffer.find(TOOL_CALL_END) else {
970 if !Self::could_start_invoke(&self.buffer) {
971 normal_text.push_str(TOOL_CALL_START);
975 self.buffer.drain(..TOOL_CALL_START.len());
976 self.in_tool_call = false;
977 continue;
978 }
979 break;
980 };
981 let block_end = end_rel + TOOL_CALL_END.len();
982 let block = self.buffer[..block_end].to_string();
983 self.buffer = self.buffer[block_end..].to_string();
984 self.in_tool_call = false;
985
986 let (block_calls, _) = Self::parse_tool_calls(&block, tools);
987 if block_calls.is_empty() {
988 normal_text.push_str(&block);
991 continue;
992 }
993 for call in block_calls {
994 if self.current_tool_id == -1 {
995 self.current_tool_id = 0;
996 } else {
997 self.current_tool_id += 1;
998 }
999 let tool_id = self.current_tool_id as usize;
1000 helpers::ensure_capacity(
1001 self.current_tool_id,
1002 &mut self.prev_tool_call_arr,
1003 &mut self.streamed_args_for_tool,
1004 );
1005
1006 let args = call.function.arguments.clone();
1007 if tool_id < self.streamed_args_for_tool.len() {
1008 self.streamed_args_for_tool[tool_id].clone_from(&args);
1009 }
1010 let parsed_args: Value =
1011 serde_json::from_str(&args).unwrap_or_else(|_| Value::Object(Map::new()));
1012 if tool_id < self.prev_tool_call_arr.len() {
1013 self.prev_tool_call_arr[tool_id] = serde_json::json!({
1014 "name": call.function.name,
1015 "arguments": parsed_args,
1016 });
1017 }
1018
1019 calls.push(ToolCallItem {
1021 tool_index: tool_id,
1022 name: Some(call.function.name),
1023 parameters: String::new(),
1024 });
1025 calls.push(ToolCallItem {
1026 tool_index: tool_id,
1027 name: None,
1028 parameters: args,
1029 });
1030 }
1031 }
1032
1033 Ok(StreamingParseResult { normal_text, calls })
1034 }
1035
1036 fn has_tool_markers(&self, text: &str) -> bool {
1037 text.contains(TOOL_CALL_START)
1038 }
1039
1040 fn get_unstreamed_tool_args(&self) -> Option<Vec<ToolCallItem>> {
1041 helpers::get_unstreamed_args(&self.prev_tool_call_arr, &self.streamed_args_for_tool)
1042 }
1043
1044 fn take_unstreamed_normal_text(&mut self) -> String {
1045 self.in_tool_call = false;
1049 std::mem::take(&mut self.buffer)
1050 }
1051
1052 fn reset(&mut self) {
1053 self.buffer.clear();
1054 self.prev_tool_call_arr.clear();
1055 self.current_tool_id = -1;
1056 self.streamed_args_for_tool.clear();
1057 self.in_tool_call = false;
1058 }
1059}