pub const TOOL_HOLD_MAX: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReasoningFormat {
DeepSeekV32,
Think,
ThinkAlwaysOpen,
MiniMaxM3,
Gemma4,
GptOss,
MuseGlimmer,
}
impl ReasoningFormat {
pub fn as_str(self) -> &'static str {
match self {
ReasoningFormat::DeepSeekV32 => "deepseekv32",
ReasoningFormat::Think => "qwen3",
ReasoningFormat::ThinkAlwaysOpen => "minimax",
ReasoningFormat::MiniMaxM3 => "minimax_m3",
ReasoningFormat::Gemma4 => "gemma4",
ReasoningFormat::GptOss => "gpt_oss",
ReasoningFormat::MuseGlimmer => "muse_glimmer",
}
}
pub fn parse(name: &str) -> Option<ReasoningFormat> {
match name {
"deepseekv32" => Some(ReasoningFormat::DeepSeekV32),
"qwen3" | "glm" => Some(ReasoningFormat::Think),
"minimax" => Some(ReasoningFormat::ThinkAlwaysOpen),
"minimax_m3" => Some(ReasoningFormat::MiniMaxM3),
"gemma4" => Some(ReasoningFormat::Gemma4),
"gpt_oss" | "gpt-oss" => Some(ReasoningFormat::GptOss),
"muse_glimmer" => Some(ReasoningFormat::MuseGlimmer),
_ => None,
}
}
pub fn infer(marker: &str) -> Option<ReasoningFormat> {
let marker = marker.to_ascii_lowercase();
let has = |needle: &str| marker.contains(needle);
if has("gpt_oss") || has("gpt-oss") || has("gptoss") {
return Some(ReasoningFormat::GptOss);
}
if has("muse") && has("glimmer") {
return Some(ReasoningFormat::MuseGlimmer);
}
if has("deepseek") && (has("v4") || has("v3.2") || has("v32")) {
return Some(ReasoningFormat::DeepSeekV32);
}
if has("deepseek") && has("r1") {
return Some(ReasoningFormat::Think);
}
if has("qwen3") {
return Some(ReasoningFormat::Think);
}
if has("glm") {
return Some(ReasoningFormat::Think);
}
if has("minimax_m3") || has("minimax-m3") || has("minimaxm3") {
return Some(ReasoningFormat::MiniMaxM3);
}
if has("minimax") {
return Some(ReasoningFormat::ThinkAlwaysOpen);
}
if has("gemma4") {
return Some(ReasoningFormat::Gemma4);
}
None
}
pub fn prompt_opens_reasoning(self, prompt: &str) -> bool {
let m = self.markers();
if m.always_open {
return true;
}
if m.start.is_empty() {
return false;
}
match prompt.rfind(m.start) {
None => false,
Some(start) => prompt.rfind(m.end).is_none_or(|end| end < start),
}
}
fn markers(self) -> Markers {
match self {
ReasoningFormat::DeepSeekV32 => Markers {
start: "<think>",
end: "</think>",
tool_start: Some("<|DSML|"),
always_open: true,
},
ReasoningFormat::Think => Markers {
start: "<think>",
end: "</think>",
tool_start: None,
always_open: false,
},
ReasoningFormat::ThinkAlwaysOpen => Markers {
start: "<think>",
end: "</think>",
tool_start: None,
always_open: true,
},
ReasoningFormat::MiniMaxM3 => Markers {
start: "<mm:think>",
end: "</mm:think>",
tool_start: Some("]<]minimax[>[<tool_call>"),
always_open: false,
},
ReasoningFormat::Gemma4 => Markers {
start: "<|channel>thought\n",
end: "<channel|>",
tool_start: None,
always_open: false,
},
ReasoningFormat::GptOss => Markers {
start: "",
end: "",
tool_start: None,
always_open: false,
},
ReasoningFormat::MuseGlimmer => Markers {
start: ATEM_START,
end: ATEM_MESSAGE,
tool_start: None,
always_open: false,
},
}
}
}
pub const ATEM_START: &str = "<|start|>";
pub const ATEM_MESSAGE: &str = "<|message|>";
pub const ATEM_CLOSING_TOKENS: [&str; 3] = ["<|eot|>", "<|eom|>", "<|end_of_text|>"];
pub const ATEM_ALL_TOKENS: [&str; 5] = [
ATEM_START,
ATEM_MESSAGE,
"<|eot|>",
"<|eom|>",
"<|end_of_text|>",
];
const ATEM_NAME_MAX: usize = 64;
pub const ATEM_HEADER_SPAN: usize = 128;
const ATEM_HOLD_WINDOW: usize = 96;
pub fn atem_marker_inside(text: &str, start: usize, end: usize) -> bool {
if start > end || end > text.len() {
return false;
}
let window = &text[start..end];
ATEM_ALL_TOKENS
.iter()
.any(|token| *token != ATEM_MESSAGE && window.contains(token))
}
pub fn atem_hold_len(text: &str) -> usize {
let owned: Vec<String> = ATEM_ALL_TOKENS.iter().map(|t| t.to_string()).collect();
let mut best = crate::policy::detokenize::stop_prefix_holdback(text, &owned);
let window_start = text
.char_indices()
.rev()
.take(ATEM_HOLD_WINDOW)
.last()
.map(|(index, _)| index)
.unwrap_or(0);
let window = &text[window_start..];
if let Some(index) = window.rfind("to=") {
let tail = &window[index..];
let name = &tail["to=".len()..];
let run: String = name
.chars()
.take_while(|c| !c.is_whitespace() && *c != '<')
.collect();
if run.len() == name.len() && run.chars().count() <= ATEM_NAME_MAX {
best = best.max(tail.len());
} else if run.chars().count() <= ATEM_NAME_MAX
&& !run.is_empty()
&& ATEM_MESSAGE.starts_with(&name[run.len()..])
&& name.len() > run.len()
{
best = best.max(tail.len());
}
}
for prefix in ["t", "to"] {
if prefix.len() > best && text.ends_with(prefix) {
best = prefix.len();
}
}
best
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AtemSwitch {
pub start: usize,
pub end: usize,
pub recipient: String,
}
pub fn find_atem_switch(text: &str) -> Option<AtemSwitch> {
let mut from = 0usize;
while let Some(index) = text[from..].find("to=").map(|i| i + from) {
let name_start = index + "to=".len();
let run: String = text[name_start..]
.chars()
.take_while(|c| !c.is_whitespace() && *c != '<')
.collect();
let count = run.chars().count();
if (1..=ATEM_NAME_MAX).contains(&count)
&& text[name_start + run.len()..].starts_with(ATEM_MESSAGE)
{
return Some(AtemSwitch {
start: index,
end: name_start + run.len() + ATEM_MESSAGE.len(),
recipient: run,
});
}
from = index + "to=".len();
}
None
}
pub fn atem_recipient(header: &str) -> Option<String> {
let index = header.find("to=")? + "to=".len();
let run: String = header[index..]
.chars()
.take_while(|c| !c.is_whitespace() && *c != '<')
.collect();
let count = run.chars().count();
(1..=ATEM_NAME_MAX).contains(&count).then_some(run)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AtemBoundary {
Closer { at: usize, token: &'static str },
Start { at: usize },
Switch(AtemSwitch),
}
impl AtemBoundary {
pub fn at(&self) -> usize {
match self {
AtemBoundary::Closer { at, .. } | AtemBoundary::Start { at } => *at,
AtemBoundary::Switch(switch) => switch.start,
}
}
}
pub fn atem_boundary(text: &str) -> Option<AtemBoundary> {
let mut best: Option<AtemBoundary> = None;
let mut consider = |candidate: AtemBoundary| {
if best.as_ref().is_none_or(|b| candidate.at() < b.at()) {
best = Some(candidate);
}
};
for token in ATEM_CLOSING_TOKENS {
if let Some(at) = text.find(token) {
consider(AtemBoundary::Closer { at, token });
}
}
if let Some(at) = text.find(ATEM_START) {
consider(AtemBoundary::Start { at });
}
if let Some(switch) = find_atem_switch(text) {
consider(AtemBoundary::Switch(switch));
}
best
}
#[derive(Debug, Clone, Copy)]
struct Markers {
start: &'static str,
end: &'static str,
tool_start: Option<&'static str>,
always_open: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ReasoningDelta {
pub reasoning: String,
pub content: String,
}
impl ReasoningDelta {
pub fn is_empty(&self) -> bool {
self.reasoning.is_empty() && self.content.is_empty()
}
}
#[derive(Debug, Default)]
struct HarmonyState {
buffer: String,
emitted_reasoning: usize,
emitted_content: usize,
passthrough: bool,
}
const HARMONY_CHANNEL: &str = "<|channel|>";
const HARMONY_MESSAGE: &str = "<|message|>";
const HARMONY_BOUNDARIES: [&str; 5] = [
"<|end|>",
"<|return|>",
"<|call|>",
"<|start|>",
"<|channel|>",
];
const HARMONY_CLOSERS: [&str; 3] = ["<|end|>", "<|return|>", "<|call|>"];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AtemMode {
Seek,
Body,
}
#[derive(Debug)]
struct AtemState {
buffer: String,
mode: AtemMode,
recipient: String,
synthetic_open: bool,
header_open: bool,
}
impl AtemState {
fn new(header_open: bool) -> Self {
AtemState {
buffer: if header_open {
ATEM_START.to_string()
} else {
String::new()
},
mode: AtemMode::Seek,
recipient: "user".to_string(),
synthetic_open: header_open,
header_open,
}
}
}
#[derive(Debug)]
pub struct ReasoningParser {
format: ReasoningFormat,
markers: Markers,
in_reasoning: bool,
stream_reasoning: bool,
buffer: String,
stripped_start: bool,
harmony: HarmonyState,
atem: AtemState,
leading_closer_pending: bool,
head_buffer: String,
}
impl ReasoningParser {
pub fn new(format: ReasoningFormat, force_reasoning: bool, stream_reasoning: bool) -> Self {
let markers = format.markers();
let force = force_reasoning || markers.always_open;
ReasoningParser {
format,
markers,
in_reasoning: force,
stream_reasoning,
buffer: String::new(),
stripped_start: false,
harmony: HarmonyState::default(),
atem: AtemState::new(force),
leading_closer_pending: format == ReasoningFormat::MiniMaxM3 && !force,
head_buffer: String::new(),
}
}
pub fn format(&self) -> ReasoningFormat {
self.format
}
pub fn parse_complete(&self, text: &str) -> ReasoningDelta {
let mut clone = ReasoningParser::new(self.format, self.in_reasoning, true);
clone.leading_closer_pending = self.leading_closer_pending;
let mut out = clone.push(text);
let tail = clone.flush();
out.reasoning.push_str(&tail.reasoning);
out.content.push_str(&tail.content);
ReasoningDelta {
reasoning: out.reasoning.trim().to_string(),
content: out.content.trim().to_string(),
}
}
pub fn push(&mut self, chunk: &str) -> ReasoningDelta {
if self.format == ReasoningFormat::GptOss {
return self.push_harmony(chunk);
}
if self.format == ReasoningFormat::MuseGlimmer {
self.atem.buffer.push_str(chunk);
return self.drain_atem(false);
}
if self.leading_closer_pending {
if let Some(delta) = self.push_leading_closer(chunk) {
return delta;
}
}
self.push_marker(chunk)
}
pub fn flush(&mut self) -> ReasoningDelta {
if self.format == ReasoningFormat::GptOss {
let (reasoning, content) = self.harmony_scan(false);
let delta = self.harmony_delta(reasoning, content);
self.harmony = HarmonyState::default();
return delta;
}
if self.format == ReasoningFormat::MuseGlimmer {
let delta = self.drain_atem(true);
self.atem = AtemState::new(self.atem.header_open);
return delta;
}
if self.leading_closer_pending && !self.head_buffer.is_empty() {
let head = std::mem::take(&mut self.head_buffer);
self.leading_closer_pending = false;
let mut delta = self.push_marker(&head);
let tail = self.flush();
delta.reasoning.push_str(&tail.reasoning);
delta.content.push_str(&tail.content);
return delta;
}
let held = std::mem::take(&mut self.buffer);
if held.is_empty() {
return ReasoningDelta::default();
}
if let Some(tool) = self.markers.tool_start {
if held.trim_start().starts_with(tool) {
self.in_reasoning = false;
return ReasoningDelta {
content: held,
..Default::default()
};
}
}
if self.in_reasoning {
ReasoningDelta {
reasoning: held,
..Default::default()
}
} else {
ReasoningDelta {
content: held,
..Default::default()
}
}
}
fn drain_atem(&mut self, at_end: bool) -> ReasoningDelta {
let mut out = ReasoningDelta::default();
while !self.atem.buffer.is_empty() {
let buf = std::mem::take(&mut self.atem.buffer);
let progressed = match self.atem.mode {
AtemMode::Seek => self.atem_seek(&buf, at_end, &mut out),
AtemMode::Body => self.atem_body(&buf, at_end, &mut out),
};
if !progressed {
break;
}
}
out
}
fn atem_seek(&mut self, buf: &str, at_end: bool, out: &mut ReasoningDelta) -> bool {
let start = buf.find(ATEM_START);
if let Some(switch) = find_atem_switch(buf) {
if start.is_none_or(|s| switch.start < s) {
push_seek_text(&mut out.content, &buf[..switch.start]);
self.atem.buffer = buf[switch.end..].to_string();
let header = buf[switch.start..switch.end].to_string();
self.atem_begin_body(&switch.recipient, &header, out);
return true;
}
}
let Some(start) = start else {
let hold = if at_end { 0 } else { atem_hold_len(buf) };
let split = crate::policy::detokenize::floor_char_boundary(buf, buf.len() - hold);
push_seek_text(&mut out.content, &buf[..split]);
self.atem.buffer = buf[split..].to_string();
return false;
};
let synthetic = self.atem.synthetic_open;
let header_start = start + ATEM_START.len();
let message = buf[header_start..]
.find(ATEM_MESSAGE)
.map(|i| i + header_start);
if atem_marker_inside(buf, header_start, message.unwrap_or(buf.len())) {
push_seek_text(&mut out.content, &buf[..start]);
self.atem_release_start(synthetic, out);
self.atem.buffer = buf[header_start..].to_string();
return true;
}
match message {
Some(message) if message - header_start > ATEM_HEADER_SPAN => {
push_seek_text(&mut out.content, &buf[..start]);
self.atem_release_start(synthetic, out);
self.atem.buffer = buf[header_start..].to_string();
true
}
Some(message) => {
push_seek_text(&mut out.content, &buf[..start]);
let recipient =
atem_recipient(&buf[header_start..message]).unwrap_or_else(|| "user".into());
let body_start = message + ATEM_MESSAGE.len();
let header = buf[start..body_start].to_string();
self.atem.synthetic_open = false;
self.atem.buffer = buf[body_start..].to_string();
self.atem_begin_body(&recipient, &header, out);
true
}
None => {
push_seek_text(&mut out.content, &buf[..start]);
self.atem.buffer = buf[start..].to_string();
let held = self.atem.buffer.len() - ATEM_START.len();
if held > ATEM_HEADER_SPAN + ATEM_MESSAGE.len() {
self.atem_release_start(synthetic, out);
self.atem.buffer = self.atem.buffer[ATEM_START.len()..].to_string();
return true;
}
if at_end {
let tail = self.atem.buffer[ATEM_START.len()..].to_string();
push_seek_text(&mut out.content, &tail);
self.atem.synthetic_open = false;
self.atem.buffer.clear();
}
false
}
}
}
fn atem_body(&mut self, buf: &str, at_end: bool, out: &mut ReasoningDelta) -> bool {
let recipient = self.atem.recipient.clone();
let Some(boundary) = atem_boundary(buf) else {
let hold = if at_end { 0 } else { atem_hold_len(buf) };
let split = crate::policy::detokenize::floor_char_boundary(buf, buf.len() - hold);
atem_emit(out, &recipient, &buf[..split]);
self.atem.buffer = buf[split..].to_string();
return false;
};
let end = boundary.at();
let mut body = buf[..end].to_string();
if !matches!(recipient.as_str(), "self" | "user") {
body.push_str(match &boundary {
AtemBoundary::Closer { token, .. } => token,
_ => "<|eom|>",
});
}
atem_emit(out, &recipient, &body);
match boundary {
AtemBoundary::Closer { token, .. } => {
self.atem.buffer = buf[end + token.len()..].to_string();
self.atem.mode = AtemMode::Seek;
}
AtemBoundary::Start { .. } => {
self.atem.buffer = buf[end..].to_string();
self.atem.mode = AtemMode::Seek;
}
AtemBoundary::Switch(switch) => {
self.atem.buffer = buf[switch.end..].to_string();
let header = buf[switch.start..switch.end].to_string();
self.atem_begin_body(&switch.recipient, &header, out);
}
}
true
}
fn atem_begin_body(&mut self, recipient: &str, header: &str, out: &mut ReasoningDelta) {
self.atem.recipient = recipient.to_string();
self.atem.mode = AtemMode::Body;
if !matches!(recipient, "self" | "user") {
atem_emit(out, recipient, header);
}
}
fn atem_release_start(&mut self, synthetic: bool, out: &mut ReasoningDelta) {
if !synthetic {
out.content.push_str(ATEM_START);
}
self.atem.synthetic_open = false;
}
fn push_leading_closer(&mut self, chunk: &str) -> Option<ReasoningDelta> {
self.head_buffer.push_str(chunk);
let head = self.head_buffer.trim_start();
if let Some(rest) = head.strip_prefix(self.markers.end) {
let rest = rest.to_string();
self.head_buffer.clear();
self.leading_closer_pending = false;
self.in_reasoning = false;
return Some(self.push_marker(&rest));
}
if is_partial_prefix(head, self.markers.end) {
return Some(ReasoningDelta::default());
}
let head = std::mem::take(&mut self.head_buffer);
self.leading_closer_pending = false;
Some(self.push_marker(&head))
}
fn push_marker(&mut self, chunk: &str) -> ReasoningDelta {
self.buffer.push_str(chunk);
if !self.stripped_start && !self.markers.start.is_empty() {
if let Some(index) = self.buffer.find(self.markers.start) {
let mut rebuilt = String::with_capacity(self.buffer.len());
rebuilt.push_str(&self.buffer[..index]);
rebuilt.push_str(&self.buffer[index + self.markers.start.len()..]);
self.buffer = rebuilt;
self.stripped_start = true;
self.in_reasoning = true;
}
}
if self.in_reasoning && !self.markers.end.is_empty() {
if let Some(end) = self.buffer.find(self.markers.end) {
let reasoning = self.buffer[..end].trim_end().to_string();
let content = self.buffer[end + self.markers.end.len()..].to_string();
self.buffer.clear();
self.in_reasoning = false;
return ReasoningDelta { reasoning, content };
}
}
if self.in_reasoning {
if let Some(tool) = self.markers.tool_start {
if let Some(index) = self.buffer.find(tool) {
let reasoning = self.buffer[..index].to_string();
let held = self.buffer[index..].to_string();
self.buffer = held.clone();
if held.len() > TOOL_HOLD_MAX {
self.buffer.clear();
self.in_reasoning = false;
return ReasoningDelta {
reasoning,
content: held,
};
}
return ReasoningDelta {
reasoning,
..Default::default()
};
}
}
}
let hold = self.trailing_partial_len();
let safe_end = self.buffer.len() - hold;
if self.in_reasoning && !self.stream_reasoning {
return ReasoningDelta::default();
}
let safe: String = self.buffer.drain(..safe_end).collect();
if safe.is_empty() {
return ReasoningDelta::default();
}
if self.in_reasoning {
ReasoningDelta {
reasoning: safe,
..Default::default()
}
} else {
ReasoningDelta {
content: safe,
..Default::default()
}
}
}
fn trailing_partial_len(&self) -> usize {
let mut candidates: Vec<&str> = vec![self.markers.end];
if !self.stripped_start && !self.markers.start.is_empty() {
candidates.push(self.markers.start);
}
if let Some(tool) = self.markers.tool_start {
candidates.push(tool);
}
candidates.retain(|c| !c.is_empty());
let owned: Vec<String> = candidates.iter().map(|c| c.to_string()).collect();
let hold = crate::policy::detokenize::stop_prefix_holdback(&self.buffer, &owned);
let split =
crate::policy::detokenize::floor_char_boundary(&self.buffer, self.buffer.len() - hold);
self.buffer.len() - split
}
fn push_harmony(&mut self, chunk: &str) -> ReasoningDelta {
self.harmony.buffer.push_str(chunk);
let (reasoning, content) = self.harmony_scan(true);
self.harmony_delta(reasoning, content)
}
fn harmony_delta(&mut self, reasoning: String, content: String) -> ReasoningDelta {
let passthrough = !self.harmony.buffer.contains(HARMONY_CHANNEL);
if passthrough != self.harmony.passthrough {
self.harmony.emitted_reasoning = 0;
self.harmony.emitted_content = 0;
self.harmony.passthrough = passthrough;
}
let new_reasoning = reasoning
.get(self.harmony.emitted_reasoning..)
.unwrap_or("")
.to_string();
let new_content = content
.get(self.harmony.emitted_content..)
.unwrap_or("")
.to_string();
self.harmony.emitted_reasoning = reasoning.len();
self.harmony.emitted_content = content.len();
ReasoningDelta {
reasoning: new_reasoning,
content: new_content,
}
}
fn harmony_scan(&self, hold_partial: bool) -> (String, String) {
let text = &self.harmony.buffer;
let mut reasoning = String::new();
let mut content = String::new();
let mut cursor = 0usize;
if !text.contains(HARMONY_CHANNEL) {
let mut body = text.as_str();
if hold_partial {
let markers = [HARMONY_CHANNEL.to_string()];
let hold = crate::policy::detokenize::stop_prefix_holdback(body, &markers);
body = &body
[..crate::policy::detokenize::floor_char_boundary(body, body.len() - hold)];
}
return (String::new(), body.to_string());
}
while let Some(channel) = text[cursor..].find(HARMONY_CHANNEL).map(|i| i + cursor) {
let header_start = channel + HARMONY_CHANNEL.len();
let Some(message) = text[header_start..]
.find(HARMONY_MESSAGE)
.map(|i| i + header_start)
else {
break;
};
let header = &text[header_start..message];
let body_start = message + HARMONY_MESSAGE.len();
let (end, matched) = earliest_marker(text, body_start, &HARMONY_BOUNDARIES);
let channel_name = header.split_whitespace().next().unwrap_or("");
let is_tool = channel_name == "commentary" && header.contains("to=functions");
if is_tool {
let slice_end = match matched {
Some(marker) if HARMONY_CLOSERS.contains(&marker) => end + marker.len(),
_ => end,
};
content.push_str(&text[channel..slice_end]);
} else {
let mut body = &text[body_start..end];
if matched.is_none() && hold_partial {
let markers: Vec<String> =
HARMONY_BOUNDARIES.iter().map(|m| m.to_string()).collect();
let hold = crate::policy::detokenize::stop_prefix_holdback(body, &markers);
body = &body
[..crate::policy::detokenize::floor_char_boundary(body, body.len() - hold)];
}
if channel_name == "analysis" {
reasoning.push_str(body);
} else {
content.push_str(body);
}
}
if matched.is_none() {
break;
}
cursor = end;
}
(reasoning, content)
}
}
fn earliest_marker<'a>(text: &str, from: usize, markers: &[&'a str]) -> (usize, Option<&'a str>) {
let mut best = text.len();
let mut which = None;
for marker in markers {
if let Some(index) = text[from..].find(marker).map(|i| i + from) {
if index < best {
best = index;
which = Some(*marker);
}
}
}
(best, which)
}
fn atem_emit(out: &mut ReasoningDelta, recipient: &str, piece: &str) {
if piece.is_empty() {
return;
}
if recipient == "self" {
out.reasoning.push_str(piece);
} else {
out.content.push_str(piece);
}
}
fn push_seek_text(content: &mut String, piece: &str) {
if piece.is_empty() {
return;
}
let mut piece = piece.to_string();
for token in ATEM_CLOSING_TOKENS {
if piece.contains(token) {
piece = piece.replace(token, "");
}
}
content.push_str(&piece);
}
fn is_partial_prefix(text: &str, marker: &str) -> bool {
!text.is_empty() && text.len() < marker.len() && marker.starts_with(text)
}
#[cfg(test)]
mod tests {
use super::*;
fn stream(parser: &mut ReasoningParser, chunks: &[&str]) -> ReasoningDelta {
let mut out = ReasoningDelta::default();
for chunk in chunks {
let delta = parser.push(chunk);
out.reasoning.push_str(&delta.reasoning);
out.content.push_str(&delta.content);
}
let tail = parser.flush();
out.reasoning.push_str(&tail.reasoning);
out.content.push_str(&tail.content);
out
}
#[test]
fn a_closed_block_splits_into_reasoning_and_answer() {
let parser = ReasoningParser::new(ReasoningFormat::Think, false, true);
let split = parser.parse_complete("<think>let me see</think>The answer is 4.");
assert_eq!(split.reasoning, "let me see");
assert_eq!(split.content, "The answer is 4.");
}
#[test]
fn text_with_no_block_is_all_answer() {
let parser = ReasoningParser::new(ReasoningFormat::Think, false, true);
let split = parser.parse_complete("Just the answer.");
assert_eq!(split.reasoning, "");
assert_eq!(split.content, "Just the answer.");
}
#[test]
fn a_forced_parser_starts_inside_the_block() {
let parser = ReasoningParser::new(ReasoningFormat::Think, true, true);
let split = parser.parse_complete("thinking hard</think>done");
assert_eq!(split.reasoning, "thinking hard");
assert_eq!(split.content, "done");
let unforced = ReasoningParser::new(ReasoningFormat::Think, false, true);
let split = unforced.parse_complete("thinking hard</think>done");
assert_eq!(split.reasoning, "");
assert_eq!(split.content, "thinking hard</think>done");
}
#[test]
fn a_marker_split_across_chunks_never_leaks() {
let mut parser = ReasoningParser::new(ReasoningFormat::Think, true, true);
let out = stream(&mut parser, &["thinking", "</thi", "nk>", "answer"]);
assert_eq!(out.reasoning, "thinking");
assert_eq!(out.content, "answer");
assert!(!out.reasoning.contains("</"), "no partial marker leaked");
}
#[test]
fn streaming_agrees_with_one_shot() {
let text = "<think>step one. step two.</think>The answer is 4.";
let reference =
ReasoningParser::new(ReasoningFormat::Think, false, true).parse_complete(text);
for width in [1usize, 3, 7, 13, 64] {
let mut parser = ReasoningParser::new(ReasoningFormat::Think, false, true);
let chunks: Vec<String> = text
.as_bytes()
.chunks(width)
.map(|c| String::from_utf8_lossy(c).into_owned())
.collect();
let refs: Vec<&str> = chunks.iter().map(String::as_str).collect();
let out = stream(&mut parser, &refs);
assert_eq!(out.reasoning.trim(), reference.reasoning, "width {width}");
assert_eq!(out.content.trim(), reference.content, "width {width}");
}
}
#[test]
fn withheld_reasoning_still_arrives_in_full() {
let mut parser = ReasoningParser::new(ReasoningFormat::Think, true, false);
let first = parser.push("thinking ");
assert!(first.is_empty(), "nothing yet");
let second = parser.push("more</think>answer");
assert_eq!(second.reasoning, "thinking more");
assert_eq!(second.content, "answer");
}
#[test]
fn a_closed_block_wins_over_a_quoted_tool_marker() {
let parser = ReasoningParser::new(ReasoningFormat::DeepSeekV32, true, true);
let split = parser.parse_complete(
"I could call <|DSML|function_calls> here, but I won't.</think>No tool needed.",
);
assert!(split.reasoning.contains("but I won't"));
assert_eq!(split.content, "No tool needed.");
}
#[test]
fn a_real_tool_call_ends_reasoning_without_a_closing_marker() {
let parser = ReasoningParser::new(ReasoningFormat::DeepSeekV32, true, true);
let payload = "x".repeat(TOOL_HOLD_MAX + 1);
let split = parser.parse_complete(&format!(
"I should look it up.<|DSML|function_calls>{payload}"
));
assert_eq!(split.reasoning, "I should look it up.");
assert!(split.content.starts_with("<|DSML|function_calls>"));
}
#[test]
fn a_truncated_stream_releases_what_it_held() {
let mut parser = ReasoningParser::new(ReasoningFormat::Think, true, true);
let out = stream(&mut parser, &["thinking</thi"]);
assert_eq!(out.reasoning, "thinking</thi");
assert!(out.content.is_empty());
}
#[test]
fn the_harmony_format_routes_analysis_to_reasoning() {
let parser = ReasoningParser::new(ReasoningFormat::GptOss, false, true);
let split = parser.parse_complete(
"<|channel|>analysis<|message|>weighing it up<|end|>\
<|channel|>final<|message|>The answer is 4.<|return|>",
);
assert_eq!(split.reasoning, "weighing it up");
assert_eq!(split.content, "The answer is 4.");
}
#[test]
fn a_harmony_tool_channel_survives_verbatim() {
let parser = ReasoningParser::new(ReasoningFormat::GptOss, false, true);
let split = parser.parse_complete(
"<|channel|>analysis<|message|>need the weather<|end|>\
<|channel|>commentary to=functions.get_weather<|message|>{\"city\":\"Rome\"}<|call|>",
);
assert_eq!(split.reasoning, "need the weather");
assert!(split.content.contains("to=functions.get_weather"));
assert!(split.content.contains("{\"city\":\"Rome\"}"));
assert!(split.content.ends_with("<|call|>"));
}
#[test]
fn harmony_text_with_no_channels_passes_through() {
let parser = ReasoningParser::new(ReasoningFormat::GptOss, false, true);
let split = parser.parse_complete("plain answer");
assert_eq!(split.content, "plain answer");
assert_eq!(split.reasoning, "");
}
#[test]
fn harmony_streams_incrementally_and_matches_one_shot() {
let text = "<|channel|>analysis<|message|>step one<|end|><|channel|>final<|message|>done<|return|>";
let reference =
ReasoningParser::new(ReasoningFormat::GptOss, false, true).parse_complete(text);
let mut parser = ReasoningParser::new(ReasoningFormat::GptOss, false, true);
let chunks: Vec<String> = text
.as_bytes()
.chunks(5)
.map(|c| String::from_utf8_lossy(c).into_owned())
.collect();
let refs: Vec<&str> = chunks.iter().map(String::as_str).collect();
let out = stream(&mut parser, &refs);
assert_eq!(out.reasoning.trim(), reference.reasoning);
assert_eq!(out.content.trim(), reference.content);
}
#[test]
fn a_leading_bare_closer_means_no_thinking_this_turn() {
let parser = ReasoningParser::new(ReasoningFormat::MiniMaxM3, false, true);
let split = parser.parse_complete("</mm:think>Straight to the answer.");
assert_eq!(split.reasoning, "");
assert_eq!(split.content, "Straight to the answer.");
}
#[test]
fn a_later_closer_is_not_the_adaptive_signal() {
let parser = ReasoningParser::new(ReasoningFormat::MiniMaxM3, false, true);
let split = parser.parse_complete("<mm:think>weighing</mm:think>done");
assert_eq!(split.reasoning, "weighing");
assert_eq!(split.content, "done");
}
#[test]
fn the_adaptive_head_is_replayed_when_it_is_not_a_closer() {
let mut parser = ReasoningParser::new(ReasoningFormat::MiniMaxM3, false, true);
let out = stream(&mut parser, &["</m", "m:th", "ought> hmm"]);
assert!(
out.content.starts_with("</mm:thought>"),
"the held head came back verbatim: {out:?}"
);
}
#[test]
fn parser_names_round_trip_and_specific_families_win_inference() {
assert_eq!(
ReasoningFormat::parse("deepseekv32"),
Some(ReasoningFormat::DeepSeekV32)
);
assert_eq!(ReasoningFormat::parse("nonsense"), None);
assert_eq!(
ReasoningFormat::infer("MiniMax-M3-Instruct"),
Some(ReasoningFormat::MiniMaxM3),
"the specific arm must beat the bare minimax arm"
);
assert_eq!(
ReasoningFormat::infer("MiniMax-M2"),
Some(ReasoningFormat::ThinkAlwaysOpen)
);
assert_eq!(
ReasoningFormat::infer("DeepSeek-V4-Flash"),
Some(ReasoningFormat::DeepSeekV32)
);
assert_eq!(ReasoningFormat::infer("llama-3.1-8b"), None);
}
#[test]
fn gemma_channel_markers_split_the_same_way() {
let parser = ReasoningParser::new(ReasoningFormat::Gemma4, false, true);
let split = parser.parse_complete("<|channel>thought\nmulling<channel|>the answer");
assert_eq!(split.reasoning, "mulling");
assert_eq!(split.content, "the answer");
}
#[test]
fn a_prompt_that_ends_inside_a_think_block_forces_reasoning() {
let f = ReasoningFormat::Think;
assert!(f.prompt_opens_reasoning("<|im_start|>assistant\n<think>\n"));
assert!(!f.prompt_opens_reasoning("<|im_start|>assistant\n"));
assert!(
!f.prompt_opens_reasoning("<think>past</think>answer<|im_end|><|im_start|>assistant\n")
);
}
#[test]
fn an_always_open_family_needs_no_evidence_and_harmony_takes_none() {
assert!(ReasoningFormat::ThinkAlwaysOpen.prompt_opens_reasoning("anything"));
assert!(!ReasoningFormat::GptOss.prompt_opens_reasoning("<think>"));
}
fn atem(name: &str, params: &[(&str, &str)]) -> String {
let body: String = params
.iter()
.map(|(k, v)| format!("<atem:parameter name=\"{k}\">{v}</atem:parameter>\n"))
.collect();
format!(
"<atem:function_calls>\n<atem:invoke name=\"{name}\">\n{body}\
</atem:invoke>\n</atem:function_calls>"
)
}
fn stream_chars(parser: &mut ReasoningParser, text: &str, width: usize) -> ReasoningDelta {
let chars: Vec<char> = text.chars().collect();
let mut out = ReasoningDelta::default();
for chunk in chars.chunks(width) {
let piece: String = chunk.iter().collect();
let delta = parser.push(&piece);
out.reasoning.push_str(&delta.reasoning);
out.content.push_str(&delta.content);
}
let tail = parser.flush();
out.reasoning.push_str(&tail.reasoning);
out.content.push_str(&tail.content);
out
}
fn muse(header_open: bool) -> ReasoningParser {
ReasoningParser::new(ReasoningFormat::MuseGlimmer, header_open, true)
}
#[test]
fn a_bare_first_header_is_completed_by_the_synthetic_start() {
let split = muse(true).parse_complete("assistant<|message|>Plain.<|eot|>");
assert_eq!(split.content, "Plain.");
assert_eq!(split.reasoning, "");
let closed = muse(false).parse_complete("assistant<|message|>Plain.<|eot|>");
assert!(
closed.content.contains("<|message|>"),
"a header-closed parser has no choice but to leak: {closed:?}"
);
}
#[test]
fn channel_bodies_are_routed_by_their_recipient() {
let split = muse(true).parse_complete(
" to=self<|message|>Let me think about this.<|eom|>\
<|start|>assistant to=user<|message|>The answer is 42.<|eot|>",
);
assert_eq!(split.reasoning, "Let me think about this.");
assert_eq!(split.content, "The answer is 42.");
}
#[test]
fn a_tool_channel_reaches_content_verbatim_with_its_header() {
let wire = format!(
" to=self<|message|>check the weather<|eom|>\
<|start|>assistant to=weather.get<|message|>{}<|eot|>",
atem("weather.get", &[("city", "Paris")])
);
let split = muse(true).parse_complete(&wire);
assert_eq!(split.reasoning, "check the weather");
assert!(
split
.content
.starts_with("<|start|>assistant to=weather.get<|message|>"),
"{:?}",
split.content
);
assert!(split.content.ends_with("<|eot|>"));
assert!(split.content.contains("<atem:function_calls>"));
}
#[test]
fn a_tool_channel_cut_off_by_the_next_header_gets_a_synthetic_terminator() {
let wire = format!(
" to=weather.get<|message|>{}<|start|>assistant to=user<|message|>done<|eot|>",
atem("weather.get", &[("city", "Rome")])
);
let split = muse(true).parse_complete(&wire);
assert!(
split.content.contains("</atem:function_calls><|eom|>"),
"the slice must be delimited: {:?}",
split.content
);
assert!(split.content.ends_with("done"));
}
#[test]
fn a_headerless_switch_ends_the_body_it_appears_in() {
let split = muse(true).parse_complete(
" to=self<|message|>quick thought to=user<|message|>Here you go.<|eot|>",
);
assert_eq!(split.reasoning, "quick thought");
assert_eq!(split.content, "Here you go.");
}
#[test]
fn the_atem_hold_back_withholds_a_switch_that_is_still_arriving() {
let mut parser = muse(true);
let first = parser.push(" to=user<|message|>send it to=we");
assert_eq!(first.content, "send it ");
assert!(!first.content.contains("to=we"));
let rest = parser.push(&format!(
"ather.get<|message|>{}<|eot|>",
atem("weather.get", &[("city", "Lima")])
));
assert!(
rest.content.starts_with("to=weather.get<|message|>"),
"the switch reassembled into a tool slice: {:?}",
rest.content
);
assert_eq!(atem_hold_len("send it to=we"), "to=we".len());
}
#[test]
fn atem_streaming_agrees_with_one_shot_at_every_chunk_width() {
let wire = format!(
" to=self<|message|>step one\nstep two<|eom|>\
<|start|>assistant to=weather.get<|message|>{}<|eot|>\
<|start|>assistant to=user<|message|>Done: 42.<|eot|>",
atem("weather.get", &[("city", "Paris")])
);
let reference = muse(true).parse_complete(&wire);
assert_eq!(reference.reasoning, "step one\nstep two");
for width in [1usize, 3, 17, 4096] {
let mut parser = muse(true);
let out = stream_chars(&mut parser, &wire, width);
assert_eq!(out.reasoning.trim(), reference.reasoning, "width {width}");
assert_eq!(out.content.trim(), reference.content, "width {width}");
}
}
#[test]
fn a_start_marker_that_opens_no_header_is_released_as_text() {
let tail = "x".repeat(400);
let mut parser = muse(true);
let out = stream_chars(
&mut parser,
&format!(" to=user<|message|>The token <|start|> opens a segment. {tail}"),
7,
);
assert!(out.content.contains("The token "), "{:?}", out.content);
assert!(out.content.contains(&tail));
let mut short = muse(true);
let out = stream_chars(&mut short, "assistant", 3);
assert_eq!(out.content, "assistant", "a short lookalike still arrives");
}
#[test]
fn a_stray_terminator_is_stripped_without_losing_the_prose() {
let mut parser = muse(true);
let out = stream_chars(&mut parser, "The token <|eot|> ends a turn.", 5);
assert!(out.content.contains("The token "), "{:?}", out.content);
assert!(out.content.contains(" ends a turn."));
assert!(!out.content.contains("<|eot|>"));
}
#[test]
fn atem_text_with_no_channels_passes_through() {
let text = "Just a plain answer, no channels.";
let split = muse(true).parse_complete(text);
assert_eq!(split.content, text);
assert_eq!(split.reasoning, "");
}
#[test]
fn a_prompt_ending_inside_a_channel_header_opens_the_header() {
let f = ReasoningFormat::MuseGlimmer;
assert!(f.prompt_opens_reasoning("<|start|>user<|message|>hi<|eot|><|start|>assistant"));
assert!(!f.prompt_opens_reasoning("<|start|>user<|message|>hi<|eot|>"));
assert_eq!(
ReasoningFormat::infer("Muse-Glimmer-40B-A3B"),
Some(ReasoningFormat::MuseGlimmer)
);
assert_eq!(
ReasoningFormat::parse("muse_glimmer"),
Some(ReasoningFormat::MuseGlimmer)
);
}
}