Skip to main content

superfluid_daemon/
codec.rs

1//! The daemon-side text codec seam.
2
3use superfluid_abi::Status;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum TurnState {
7    AfterInput,
8    MidTurn,
9    AfterFinishedTurn,
10}
11
12#[derive(Debug, Clone, Default)]
13pub struct ToolCallMsg {
14    pub id: String,
15    pub name: String,
16    pub arguments: String,
17}
18
19#[derive(Debug, Clone, Default)]
20pub struct ChatMessage {
21    pub role: u32,
22    pub content: String,
23    pub parts: Vec<ContentPart>,
24    pub tool_calls: Vec<ToolCallMsg>,
25    pub tool_call_id: Option<String>,
26    pub name: Option<String>,
27    pub reasoning: Option<String>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum ContentPart {
32    Text(String),
33    Image { blob: String },
34    Audio { blob: String },
35}
36
37impl ChatMessage {
38    pub fn new(role: u32, content: impl Into<String>) -> ChatMessage {
39        ChatMessage { role, content: content.into(), ..Default::default() }
40    }
41}
42
43pub(crate) fn thinking_probe_contexts() -> Vec<(Vec<ChatMessage>, Vec<String>)> {
44    let user = ChatMessage::new(crate::wal::role::USER, "probe");
45    let system = ChatMessage::new(crate::wal::role::SYSTEM, "probe");
46    let tool = r#"{"type":"function","function":{"name":"probe","description":"probe","parameters":{"type":"object","properties":{}}}}"#;
47    vec![
48        (vec![user.clone()], Vec::new()),
49        (vec![system.clone(), user.clone()], Vec::new()),
50        (vec![system, user], vec![tool.to_string()]),
51    ]
52}
53
54#[cfg_attr(not(feature = "basert"), allow(dead_code))]
55pub(crate) fn thinking_switch_changes<T: PartialEq>(
56    render: impl Fn(&[ChatMessage], &[String], bool) -> Option<T>,
57) -> bool {
58    thinking_probe_contexts().iter().any(|(msgs, tools)| {
59        match (render(msgs, tools, true), render(msgs, tools, false)) {
60            (Some(on), Some(off)) => on != off,
61            (Some(_), None) | (None, Some(_)) => true,
62            (None, None) => false,
63        }
64    })
65}
66
67pub trait TextCodec: Send {
68    fn encode(&self, text: &str) -> Vec<u32>;
69
70    fn encode_content(&self, text: &str) -> Vec<u32> {
71        self.encode(text)
72    }
73
74    fn encode_pieces(&self, pieces: &[(&str, bool)]) -> Vec<u32> {
75        let mut out = Vec::new();
76        for (text, content) in pieces {
77            out.extend(if *content { self.encode_content(text) } else { self.encode(text) });
78        }
79        out
80    }
81    fn token_bytes(&self, token: u32) -> Vec<u8>;
82
83    fn decode(&self, tokens: &[u32]) -> String {
84        let mut s = Utf8Stream::default();
85        let mut out = String::new();
86        for &t in tokens {
87            out.push_str(&s.push(&self.token_bytes(t)));
88        }
89        out.push_str(&s.flush());
90        out
91    }
92
93    fn stream_prologue(&self) -> Vec<u32> {
94        Vec::new()
95    }
96
97    fn behavior_fingerprint(&self) -> Option<superfluid_fingerprint::BehaviorFingerprint> {
98        None
99    }
100
101    fn chat_template_source(&self) -> Option<String> {
102        None
103    }
104
105    fn render_message(&self, _role: u32, _text: &str) -> Option<Vec<u32>> {
106        None
107    }
108
109    fn generation_prefix(&self, _state: TurnState) -> Option<Vec<u32>> {
110        None
111    }
112
113    fn trailing_generation_prompt(
114        &self,
115        _messages: &[ChatMessage],
116        _tools: &[String],
117        kwargs: &serde_json::Map<String, serde_json::Value>,
118        span: &[u32],
119    ) -> usize {
120        let thinking = kwargs.get("enable_thinking").and_then(|v| v.as_bool());
121        [
122            self.generation_prefix_with(TurnState::AfterInput, thinking),
123            self.generation_prefix(TurnState::AfterInput),
124        ]
125        .into_iter()
126        .flatten()
127        .find(|op| !op.is_empty() && span.len() > op.len() && span.ends_with(op))
128        .map_or(0, |op| op.len())
129    }
130
131    fn empty_think_tokens(&self) -> Option<Vec<u32>> {
132        None
133    }
134
135    fn generation_prefix_with(&self, state: TurnState, thinking: Option<bool>) -> Option<Vec<u32>> {
136        let mut op = self.generation_prefix(state)?;
137        if thinking == Some(false) {
138            if let Some(block) = self.empty_think_tokens() {
139                op.extend(block);
140            }
141        }
142        Some(op)
143    }
144
145    fn render_system_full(
146        &self,
147        system: Option<&str>,
148        tool_jsons: &[String],
149        _kwargs: &serde_json::Map<String, serde_json::Value>,
150    ) -> Option<Vec<u32>> {
151        self.render_system_with_tools(system, tool_jsons)
152    }
153
154    fn supports_reasoning_effort(&self) -> bool {
155        false
156    }
157
158    fn supports_enable_thinking(&self) -> bool {
159        false
160    }
161
162    fn reasoning_effort_levels(&self) -> Vec<&'static str> {
163        Vec::new()
164    }
165
166    fn validate_template_kwargs(
167        &self,
168        _kwargs: &serde_json::Map<String, serde_json::Value>,
169    ) -> Result<(), String> {
170        Ok(())
171    }
172
173    fn render_prompt_structured_with(
174        &self,
175        messages: &[ChatMessage],
176        tools: &[String],
177        _kwargs: &serde_json::Map<String, serde_json::Value>,
178    ) -> Option<Vec<u32>> {
179        self.render_prompt_structured(messages, tools)
180    }
181
182    fn template_refusal(
183        &self,
184        _messages: &[ChatMessage],
185        _tools: &[String],
186        _kwargs: &serde_json::Map<String, serde_json::Value>,
187    ) -> Option<String> {
188        None
189    }
190
191    fn turn_separator(&self) -> Option<Vec<u32>> {
192        None
193    }
194
195    fn channel_markers(&self) -> Vec<(u32, u32, u32)> {
196        Vec::new()
197    }
198
199    fn render_system_with_tools(
200        &self,
201        _system: Option<&str>,
202        _tool_jsons: &[String],
203    ) -> Option<Vec<u32>> {
204        None
205    }
206
207    fn render_tool_result(&self, _name: &str, _content: &str) -> Option<Vec<u32>> {
208        None
209    }
210
211    fn render_assistant_with_tool_calls(
212        &self,
213        _content: &str,
214        _calls: &[(String, String)],
215    ) -> Option<Vec<u32>> {
216        None
217    }
218
219    /// The same turn where no user query follows it in the conversation (a
220    /// step of a tool loop): some templates render that turn differently from
221    /// one a later query put in history (Qwen's open its reasoning block).
222    fn render_assistant_with_tool_calls_after_query(
223        &self,
224        content: &str,
225        calls: &[(String, String)],
226    ) -> Option<Vec<u32>> {
227        self.render_assistant_with_tool_calls(content, calls)
228    }
229
230    fn render_fim(&self, _prefix: &str, _suffix: &str, _mode: u8) -> Option<Vec<u32>> {
231        None
232    }
233
234    fn render_turn_with_tokens(
235        &self,
236        _role: u32,
237        _pre_text: &str,
238        _inner: &[u32],
239        _post_text: &str,
240    ) -> Option<Vec<u32>> {
241        None
242    }
243
244    fn render_block(&self, role: u32, kind: u32, payload: &str) -> Option<Vec<u32>> {
245        let text = project_block_text(kind, payload)?;
246        if text.is_empty() {
247            return Some(Vec::new());
248        }
249        self.render_message(role, &text)
250    }
251
252    fn channelizer(&self) -> Box<dyn Channelizer> {
253        Box::new(Segmenter::new(self.channel_markers()))
254    }
255
256    fn turn_terminators(&self) -> Vec<u32> {
257        Vec::new()
258    }
259
260    fn render_prompt_structured(
261        &self,
262        messages: &[ChatMessage],
263        tools: &[String],
264    ) -> Option<Vec<u32>> {
265        let flat: Vec<(u32, String)> =
266            messages.iter().map(|m| (m.role, m.content.clone())).collect();
267        self.render_prompt(&flat, tools)
268    }
269
270    fn render_prompt(&self, messages: &[(u32, String)], tools: &[String]) -> Option<Vec<u32>> {
271        let mut out = self.stream_prologue();
272        let mut tools_emitted = tools.is_empty();
273        for (role, text) in messages {
274            if !tools_emitted && *role == crate::wal::role::SYSTEM {
275                out.extend(self.render_system_with_tools(Some(text), tools)?);
276                tools_emitted = true;
277            } else {
278                out.extend(self.render_message(*role, text)?);
279            }
280        }
281        if !tools_emitted {
282            let mut with = self.render_system_with_tools(None, tools)?;
283            with.extend(std::mem::take(&mut out));
284            out = with;
285        }
286        if let Some(op) = self.generation_prefix(TurnState::AfterInput) {
287            out.extend(op);
288        }
289        Some(out)
290    }
291
292    fn call_grammar(&self, tool_jsons: &[String]) -> Option<String> {
293        self.tool_envelope().call_grammar(tool_jsons)
294    }
295
296    fn renders_per_message(&self) -> bool {
297        true
298    }
299
300    fn tool_envelope(&self) -> ToolEnvelope {
301        ToolEnvelope::Json
302    }
303
304    fn tool_fragment_mode(&self) -> crate::tool_fragment::ToolFragmentMode {
305        match self.tool_envelope() {
306            ToolEnvelope::Json => crate::tool_fragment::ToolFragmentMode::Json,
307            _ => crate::tool_fragment::ToolFragmentMode::None,
308        }
309    }
310
311    fn tool_call_delimiters(&self) -> Option<(String, String)> {
312        let (open, close, _) = self
313            .channel_markers()
314            .into_iter()
315            .find(|&(_, _, ch)| ch == crate::wal::channel::TOOL_CALL)?;
316        Some((self.marker_literal(open), self.marker_literal(close)))
317    }
318
319    fn marker_literal(&self, token: u32) -> String {
320        self.decode(&[token])
321    }
322
323    fn structural_tag(&self, tool_jsons: &[String], at_least_one: bool) -> Option<String> {
324        let (begin, end) = self.tool_call_delimiters()?;
325        self.tool_envelope().structural_tag(tool_jsons, &begin, &end, at_least_one)
326    }
327
328    fn response_format_tag(&self, json_schema: &str) -> Option<String> {
329        let _ = json_schema;
330        None
331    }
332
333    fn parse_tool_call(&self, raw: &str) -> Option<(String, String)> {
334        self.parse_tool_call_with(raw, None)
335    }
336
337    fn parse_tool_call_with(&self, raw: &str, schemas: Option<&ToolSchemas>) -> Option<(String, String)> {
338        self.tool_envelope().parse_call(raw, schemas)
339    }
340
341    fn named_tool_call(&self, raw: &str) -> Option<(String, String)> {
342        self.tool_envelope().named_call(raw)
343    }
344}
345
346#[derive(Debug, Clone, PartialEq)]
347pub struct Run {
348    pub span: Vec<u32>,
349    pub text: Vec<u32>,
350    pub channel: u32,
351    pub closes: bool,
352}
353
354pub(crate) fn repair_json_object(raw: &str) -> Option<String> {
355    let unfenced = strip_code_fence(raw).trim();
356    let mut attempts: Vec<String> = vec![unfenced.to_string()];
357    if let Some(obj) = first_json_object(unfenced) {
358        attempts.push(obj.to_string());
359    }
360    for i in 0..attempts.len() {
361        let a = attempts[i].clone();
362        let fixed = drop_trailing_commas(&a);
363        if fixed != a {
364            attempts.push(fixed.clone());
365        }
366        if let Some(closed) = close_unterminated(&a) {
367            attempts.push(closed.clone());
368            let closed_fixed = drop_trailing_commas(&closed);
369            if closed_fixed != closed {
370                attempts.push(closed_fixed);
371            }
372        }
373    }
374    attempts.iter().find_map(|a| match serde_json::from_str::<serde_json::Value>(a) {
375        Ok(v) if v.is_object() => Some(v.to_string()),
376        _ => None,
377    })
378}
379
380fn strip_code_fence(raw: &str) -> &str {
381    let t = raw.trim();
382    let Some(rest) = t.strip_prefix("```") else { return t };
383    let rest = match rest.find('\n') {
384        Some(i) => &rest[i + 1..],
385        None => rest,
386    };
387    rest.strip_suffix("```").unwrap_or(rest).trim()
388}
389
390fn first_json_object(raw: &str) -> Option<&str> {
391    let start = raw.find('{')?;
392    let (mut depth, mut in_str, mut esc) = (0i32, false, false);
393    for (i, c) in raw.char_indices().skip_while(|(i, _)| *i < start) {
394        if esc {
395            esc = false;
396            continue;
397        }
398        match c {
399            '\\' if in_str => esc = true,
400            '"' => in_str = !in_str,
401            '{' if !in_str => depth += 1,
402            '}' if !in_str => {
403                depth -= 1;
404                if depth == 0 {
405                    return raw.get(start..i + c.len_utf8());
406                }
407            }
408            _ => {}
409        }
410    }
411    None
412}
413
414fn close_unterminated(raw: &str) -> Option<String> {
415    let mut stack: Vec<char> = Vec::new();
416    let (mut in_str, mut esc) = (false, false);
417    for c in raw.chars() {
418        if esc {
419            esc = false;
420            continue;
421        }
422        match c {
423            '\\' if in_str => esc = true,
424            '"' => in_str = !in_str,
425            '{' | '[' if !in_str => stack.push(c),
426            '}' | ']' if !in_str => {
427                stack.pop()?;
428            }
429            _ => {}
430        }
431    }
432    if stack.is_empty() && !in_str {
433        return None;
434    }
435    let mut out = raw.to_string();
436    if esc {
437        out.pop();
438    }
439    if in_str {
440        out.push('"');
441    }
442    while let Some(open) = stack.pop() {
443        out.push(if open == '{' { '}' } else { ']' });
444    }
445    Some(out)
446}
447
448fn drop_trailing_commas(raw: &str) -> String {
449    let mut out = String::with_capacity(raw.len());
450    let (mut in_str, mut esc) = (false, false);
451    let chars: Vec<char> = raw.chars().collect();
452    for (i, &c) in chars.iter().enumerate() {
453        if esc {
454            esc = false;
455            out.push(c);
456            continue;
457        }
458        match c {
459            '\\' if in_str => {
460                esc = true;
461                out.push(c);
462            }
463            '"' => {
464                in_str = !in_str;
465                out.push(c);
466            }
467            ',' if !in_str => {
468                let next = chars[i + 1..].iter().find(|c| !c.is_whitespace());
469                if matches!(next, Some('}') | Some(']')) {
470                    continue;
471                }
472                out.push(c);
473            }
474            _ => out.push(c),
475        }
476    }
477    out
478}
479
480fn call_fields(v: &serde_json::Value) -> Option<(String, String)> {
481    let v = v.get("function").filter(|f| f.is_object()).unwrap_or(v);
482    let name = v.get("name")?.as_str()?.trim().to_string();
483    if name.is_empty() {
484        return None;
485    }
486    let args = v
487        .get("arguments")
488        .or_else(|| v.get("parameters"))
489        .or_else(|| v.get("args"))
490        .or_else(|| v.get("input"));
491    let args = match args {
492        None => "{}".to_string(),
493        Some(serde_json::Value::String(s)) => {
494            let inner: serde_json::Value = serde_json::from_str(s).ok()?;
495            inner.to_string()
496        }
497        Some(other) => other.to_string(),
498    };
499    Some((name, args))
500}
501
502pub fn parse_json_tool_call(raw: &str) -> Option<(String, String)> {
503    let unfenced = strip_code_fence(raw);
504    let mut attempts: Vec<String> = vec![unfenced.to_string()];
505    if let Some(obj) = first_json_object(unfenced) {
506        attempts.push(obj.to_string());
507    }
508    for i in 0..attempts.len() {
509        let a = attempts[i].clone();
510        let fixed = drop_trailing_commas(&a);
511        if fixed != a {
512            attempts.push(fixed.clone());
513        }
514        if let Some(closed) = close_unterminated(&a) {
515            attempts.push(closed.clone());
516            let closed_fixed = drop_trailing_commas(&closed);
517            if closed_fixed != closed {
518                attempts.push(closed_fixed);
519            }
520        }
521    }
522    for a in attempts {
523        if let Ok(v) = serde_json::from_str::<serde_json::Value>(a.trim()) {
524            if let Some(call) = call_fields(&v) {
525                return Some(call);
526            }
527        }
528    }
529    None
530}
531const GEMMA_QUOTE: [char; 5] = ['<', '|', '"', '|', '>'];
532
533fn gemma_at(cs: &[char], p: usize, pat: &[char]) -> bool {
534    p + pat.len() <= cs.len() && cs[p..p + pat.len()] == *pat
535}
536
537fn gemma_skip_ws(cs: &[char], p: &mut usize) {
538    while *p < cs.len() && matches!(cs[*p], ' ' | '\n' | '\t') {
539        *p += 1;
540    }
541}
542
543fn gemma_parse_key(cs: &[char], p: &mut usize) -> String {
544    let start = *p;
545    while *p < cs.len() && !matches!(cs[*p], ':' | ',' | '}' | ']') {
546        *p += 1;
547    }
548    cs[start..*p].iter().collect()
549}
550
551fn gemma_parse_string(cs: &[char], p: &mut usize) -> String {
552    *p += GEMMA_QUOTE.len();
553    let mut i = *p;
554    while i < cs.len() && !gemma_at(cs, i, &GEMMA_QUOTE) {
555        i += 1;
556    }
557    if i >= cs.len() {
558        let v: String = cs[*p..].iter().collect();
559        *p = cs.len();
560        return v;
561    }
562    let v: String = cs[*p..i].iter().collect();
563    *p = i + GEMMA_QUOTE.len();
564    v
565}
566
567const GEMMA_MAX_DEPTH: usize = 128;
568
569fn gemma_parse_value(cs: &[char], p: &mut usize, in_array: bool) -> serde_json::Value {
570    gemma_parse_value_at(cs, p, in_array, 0)
571}
572
573fn gemma_parse_value_at(cs: &[char], p: &mut usize, in_array: bool, depth: usize) -> serde_json::Value {
574    use serde_json::Value;
575    gemma_skip_ws(cs, p);
576    if *p >= cs.len() {
577        return Value::Null;
578    }
579    if depth > GEMMA_MAX_DEPTH {
580        *p = cs.len();
581        return Value::Null;
582    }
583    if gemma_at(cs, *p, &GEMMA_QUOTE) {
584        return Value::String(gemma_parse_string(cs, p));
585    }
586    if cs[*p] == '{' {
587        *p += 1;
588        let mut obj = serde_json::Map::new();
589        while *p < cs.len() && cs[*p] != '}' {
590            while *p < cs.len() && matches!(cs[*p], ' ' | ',' | '\n') {
591                *p += 1;
592            }
593            if *p >= cs.len() || cs[*p] == '}' {
594                break;
595            }
596            let mut k = gemma_parse_key(cs, p);
597            while k.ends_with(' ') || k.ends_with('\n') {
598                k.pop();
599            }
600            if *p >= cs.len() || cs[*p] != ':' {
601                break;
602            }
603            *p += 1;
604            let v = gemma_parse_value_at(cs, p, false, depth + 1);
605            obj.insert(k, v);
606        }
607        if *p < cs.len() && cs[*p] == '}' {
608            *p += 1;
609        }
610        return Value::Object(obj);
611    }
612    if cs[*p] == '[' {
613        *p += 1;
614        let mut arr = Vec::new();
615        while *p < cs.len() && cs[*p] != ']' {
616            while *p < cs.len() && matches!(cs[*p], ' ' | ',' | '\n') {
617                *p += 1;
618            }
619            if *p >= cs.len() || cs[*p] == ']' {
620                break;
621            }
622            arr.push(gemma_parse_value_at(cs, p, true, depth + 1));
623        }
624        if *p < cs.len() && cs[*p] == ']' {
625            *p += 1;
626        }
627        return Value::Array(arr);
628    }
629    let start = *p;
630    let (mut brace, mut bracket) = (0i32, 0i32);
631    while *p < cs.len() {
632        match cs[*p] {
633            '{' => brace += 1,
634            '}' => {
635                if brace == 0 {
636                    break;
637                }
638                brace -= 1;
639            }
640            '[' => bracket += 1,
641            ']' => {
642                if bracket == 0 {
643                    break;
644                }
645                bracket -= 1;
646            }
647            ',' if brace == 0 && bracket == 0 => {
648                if in_array {
649                    break;
650                }
651                let mut q = *p + 1;
652                while q < cs.len() && matches!(cs[q], ' ' | '\n' | '\t') {
653                    q += 1;
654                }
655                let ks = q;
656                while q < cs.len() && (cs[q].is_ascii_alphanumeric() || cs[q] == '_') {
657                    q += 1;
658                }
659                if q > ks && q < cs.len() && cs[q] == ':' {
660                    break;
661                }
662            }
663            _ => {}
664        }
665        *p += 1;
666    }
667    let lit: String = cs[start..*p].iter().collect();
668    let t = lit.trim();
669    match t {
670        "true" => Value::Bool(true),
671        "false" => Value::Bool(false),
672        "null" | "" => Value::Null,
673        _ => {
674            if let Ok(i) = t.parse::<i64>() {
675                return Value::Number(i.into());
676            }
677            if let Ok(f) = t.parse::<f64>() {
678                if let Some(n) = serde_json::Number::from_f64(f) {
679                    return Value::Number(n);
680                }
681            }
682            Value::String(t.to_string())
683        }
684    }
685}
686
687pub fn trailing_open_channel(tokens: &[u32], markers: &[(u32, u32, u32)]) -> u32 {
688    let mut cur = crate::wal::channel::TEXT;
689    for &t in tokens {
690        if cur == crate::wal::channel::TEXT {
691            if let Some((_, _, ch)) = markers.iter().find(|(o, _, _)| *o == t) {
692                cur = *ch;
693            }
694        } else if markers.iter().any(|(_, c, ch)| *c == t && *ch == cur) {
695            cur = crate::wal::channel::TEXT;
696        }
697    }
698    cur
699}
700
701pub fn primed_channelizer(codec: &dyn TextCodec, prompt: &[u32]) -> Box<dyn Channelizer> {
702    let mut chan = codec.channelizer();
703    let open = trailing_open_channel(prompt, &codec.channel_markers());
704    if open != crate::wal::channel::TEXT {
705        chan.prime(open);
706    }
707    chan
708}
709
710#[cfg(test)]
711mod trailing_channel_tests {
712    use super::trailing_open_channel;
713    use crate::wal::channel;
714
715    #[test]
716    fn reports_the_channel_a_prompt_leaves_open() {
717        let m = [(10, 11, channel::REASONING), (20, 21, channel::TOOL_CALL)];
718        assert_eq!(trailing_open_channel(&[1, 2, 3], &m), channel::TEXT);
719        assert_eq!(trailing_open_channel(&[1, 2, 10, 5], &m), channel::REASONING);
720        assert_eq!(trailing_open_channel(&[1, 10, 5, 11, 5], &m), channel::TEXT);
721        assert_eq!(trailing_open_channel(&[10, 7, 11, 8, 20, 9, 21, 10], &m), channel::REASONING);
722        assert_eq!(trailing_open_channel(&[10, 21], &m), channel::REASONING);
723    }
724
725    #[test]
726    fn primed_channelizer_starts_where_the_prompt_left_off() {
727        use super::{primed_channelizer, MockChatCodec, MOCK_THINK_CLOSE, MOCK_THINK_OPEN};
728        let codec = MockChatCodec;
729        let generated = [7u32, 8, MOCK_THINK_CLOSE, 9];
730        let open = primed_channelizer(&codec, &[1, 2, MOCK_THINK_OPEN]).split(&generated);
731        assert_eq!(open[0].channel, channel::REASONING, "{open:?}");
732        assert_eq!(open.last().unwrap().channel, channel::TEXT, "{open:?}");
733        let closed =
734            primed_channelizer(&codec, &[1, MOCK_THINK_OPEN, MOCK_THINK_CLOSE]).split(&generated[..2]);
735        assert!(closed.iter().all(|r| r.channel == channel::TEXT), "{closed:?}");
736    }
737}
738
739#[derive(Clone)]
740pub struct Delims {
741    name_open: String,
742    name_close: String,
743    key_open: String,
744    key_close: String,
745    val_close: String,
746}
747
748pub(crate) const S_NAME: &str = "N1nfx";
749pub(crate) const S_K1: &str = "K1kax";
750pub(crate) const S_V1: &str = "V1vax";
751pub(crate) const S_K2: &str = "J2kbx";
752pub(crate) const S_V2: &str = "U2vbx";
753
754pub(crate) fn common_prefix(a: &str, b: &str) -> String {
755    let mut end = 0;
756    for (ca, cb) in a.chars().zip(b.chars()) {
757        if ca != cb {
758            break;
759        }
760        end += ca.len_utf8();
761    }
762    a[..end].to_string()
763}
764
765pub(crate) fn common_suffix(a: &str, b: &str) -> String {
766    let mut n = 0;
767    for (ca, cb) in a.chars().rev().zip(b.chars().rev()) {
768        if ca != cb {
769            break;
770        }
771        n += ca.len_utf8();
772    }
773    a[a.len() - n..].to_string()
774}
775
776pub(crate) fn learn_delims(two: &str, zero: Option<&str>, frame: Option<(&str, &str)>) -> Option<Delims> {
777    let at = |s: &str| two.find(s);
778    let inm = at(S_NAME)?;
779    let mut pairs = [
780        (at(S_K1)?, S_K1.len(), at(S_V1)?, S_V1.len()),
781        (at(S_K2)?, S_K2.len(), at(S_V2)?, S_V2.len()),
782    ];
783    pairs.sort_by_key(|p| p.0);
784    let (ika, kalen, iva, valen) = pairs[0];
785    let (ikb, kblen, ivb, vblen) = pairs[1];
786    if !(ika < iva && iva < ikb && ikb < ivb) {
787        return None;
788    }
789
790    let anchor_end = frame
791        .and_then(|(o, _)| two[..inm].rfind(o).map(|i| i + o.len()))
792        .unwrap_or_else(|| two[..inm].rfind('\n').map(|i| i + 1).unwrap_or(0));
793    let name_open = two[anchor_end..inm].trim().to_string();
794
795    let after_name_two = &two[inm + S_NAME.len()..];
796    let name_close = match zero.and_then(|z| z.find(S_NAME).map(|i| &z[i + S_NAME.len()..])) {
797        Some(after_name_zero) => common_prefix(after_name_two, after_name_zero),
798        None => {
799            after_name_two[..after_name_two.find(&two[ika..ika + kalen]).unwrap_or(after_name_two.len())].to_string()
800        }
801    };
802
803    let mut key_open = common_suffix(&two[..ika], &two[..ikb]);
804    let key_close = common_prefix(&two[ika + kalen..], &two[ikb + kblen..]);
805    let val_close = common_prefix(&two[iva + valen..], &two[ivb + vblen..]);
806
807    while !name_close.is_empty() && key_open.starts_with(&name_close) {
808        key_open = key_open[name_close.len()..].to_string();
809    }
810
811    if name_open.is_empty() || name_close.is_empty() || key_open.is_empty() || key_close.is_empty() {
812        return None;
813    }
814    Some(Delims {
815        name_open,
816        name_close,
817        key_open,
818        key_close,
819        val_close,
820    })
821}
822
823pub(crate) fn typed_value(s: &str) -> serde_json::Value {
824    let t = s.trim();
825    match serde_json::from_str::<serde_json::Value>(t) {
826        Ok(v @ (serde_json::Value::Object(_) | serde_json::Value::Array(_))) => v,
827        Ok(v @ (serde_json::Value::Number(_) | serde_json::Value::Bool(_) | serde_json::Value::Null))
828            if !t.is_empty() =>
829        {
830            v
831        }
832        _ => serde_json::Value::String(s.to_string()),
833    }
834}
835
836impl Delims {
837    pub(crate) fn structural_tag(
838        &self,
839        tool_jsons: &[String],
840        begin: &str,
841        end: &str,
842        at_least_one: bool,
843    ) -> Option<String> {
844        if tool_jsons.is_empty() || begin.is_empty() || end.is_empty() || !at_least_one {
845            return None;
846        }
847        let mut tags = Vec::new();
848        for t in tool_jsons {
849            let v: serde_json::Value = serde_json::from_str(t).ok()?;
850            let f = v.get("function").unwrap_or(&v);
851            let name = f.get("name").and_then(|n| n.as_str())?;
852            let params = f
853                .get("parameters")
854                .cloned()
855                .unwrap_or_else(|| serde_json::json!({ "type": "object" }));
856            tags.push(serde_json::json!({
857                "type": "tag",
858                "begin": begin,
859                "content": self.call_body_format(name, &params),
860                "end": end,
861            }));
862        }
863        Some(wrap_structural_tag(serde_json::json!({
864            "type": "triggered_tags",
865            "triggers": [begin],
866            "tags": tags,
867            "at_least_one": at_least_one,
868            "stop_after_first": stop_after_first(at_least_one),
869        })))
870    }
871
872    fn call_body_format(&self, name: &str, params: &serde_json::Value) -> serde_json::Value {
873        let props = params.get("properties").and_then(|p| p.as_object());
874        let required: Vec<String> = params
875            .get("required")
876            .and_then(|r| r.as_array())
877            .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
878            .unwrap_or_default();
879        let param = |key: &str| -> serde_json::Value {
880            let schema = props.and_then(|p| p.get(key));
881            let open = format!("{}{key}{}", self.key_open, self.key_close);
882            if self.val_close.is_empty() {
883                let schema = schema.cloned().unwrap_or_else(|| serde_json::json!({ "type": "string" }));
884                return serde_json::json!({
885                    "type": "sequence",
886                    "elements": [
887                        { "type": "const_string", "value": open },
888                        value_format(&schema, ATEM_STRING),
889                    ]
890                });
891            }
892            serde_json::json!({
893                "type": "tag",
894                "begin": open,
895                "content": glm_value_format(schema, params),
896                "end": self.val_close,
897            })
898        };
899        let all: Vec<String> = props.map(|p| p.keys().cloned().collect()).unwrap_or_default();
900        let mut elements = vec![
901            serde_json::json!({ "type": "regex", "pattern": "\\s*" }),
902            serde_json::json!({
903                "type": "const_string",
904                "value": format!("{}{name}{}", self.name_open, self.name_close),
905            }),
906        ];
907        for key in &all {
908            let e = param(key);
909            if required.contains(key) {
910                elements.push(e);
911            } else {
912                elements.push(serde_json::json!({ "type": "optional", "content": e }));
913            }
914        }
915        elements.push(serde_json::json!({ "type": "any_text" }));
916        serde_json::json!({ "type": "sequence", "elements": elements })
917    }
918
919    fn value(&self, name: &str, key: &str, raw: &str, schemas: Option<&ToolSchemas>) -> serde_json::Value {
920        let prop = schemas.and_then(|s| s.property(name, key));
921        let root = schemas.and_then(|s| s.params(name)).unwrap_or(&serde_json::Value::Null);
922        declared_value(raw, prop, root, StringUnion::Verbatim)
923    }
924
925    pub(crate) fn parse(&self, raw: &str, schemas: Option<&ToolSchemas>) -> Option<(String, String)> {
926        let i = raw.find(&self.name_open)?;
927        let j = i + self.name_open.len();
928        let ne = raw[j..].find(&self.name_close).unwrap_or(raw.len() - j);
929        let name = raw[j..j + ne].trim();
930        if name.is_empty() || name.contains('<') || name.contains(char::is_whitespace) {
931            return None;
932        }
933        let mut args = serde_json::Map::new();
934        let mut cursor = j + ne;
935        while let Some(k) = raw[cursor..].find(&self.key_open) {
936            let ks = cursor + k + self.key_open.len();
937            let ke = match raw[ks..].find(&self.key_close) {
938                Some(e) => e,
939                None => break,
940            };
941            let key = raw[ks..ks + ke].trim().to_string();
942            let vs = ks + ke + self.key_close.len();
943            let ve = if self.val_close.is_empty() {
944                raw.len() - vs
945            } else {
946                raw[vs..].find(&self.val_close).unwrap_or(raw.len() - vs)
947            };
948            let val = &raw[vs..vs + ve];
949            if !key.is_empty() {
950                let v = self.value(name, &key, val, schemas);
951                args.insert(key, v);
952            }
953            cursor = vs + ve;
954        }
955        Some((name.to_string(), serde_json::Value::Object(args).to_string()))
956    }
957
958    #[cfg(test)]
959    pub(crate) fn for_test(name_open: &str, name_close: &str, key_open: &str, key_close: &str, val_close: &str) -> Self {
960        Self {
961            name_open: name_open.into(),
962            name_close: name_close.into(),
963            key_open: key_open.into(),
964            key_close: key_close.into(),
965            val_close: val_close.into(),
966        }
967    }
968
969    pub(crate) fn peek_name(&self, raw: &str) -> Option<String> {
970        let i = raw.find(&self.name_open)?;
971        let j = i + self.name_open.len();
972        let ne = raw[j..].find(&self.name_close)?;
973        let name = raw[j..j + ne].trim();
974        if name.is_empty() || name.contains('<') || name.contains(char::is_whitespace) {
975            return None;
976        }
977        Some(name.to_string())
978    }
979
980    pub(crate) fn closed_args(
981        &self,
982        raw: &str,
983        cursor: &mut Option<usize>,
984        schemas: Option<&ToolSchemas>,
985    ) -> Vec<(String, serde_json::Value)> {
986        let mut out = Vec::new();
987        let name = schemas.and_then(|_| self.peek_name(raw)).unwrap_or_default();
988        if cursor.is_none() {
989            let Some(i) = raw.find(&self.name_open) else {
990                return out;
991            };
992            let j = i + self.name_open.len();
993            let Some(ne) = raw[j..].find(&self.name_close) else {
994                return out;
995            };
996            *cursor = Some(j + ne);
997        }
998        let cursor = cursor.as_mut().expect("set above");
999        while let Some(k) = raw[*cursor..].find(&self.key_open) {
1000            let ks = *cursor + k + self.key_open.len();
1001            let Some(ke) = raw[ks..].find(&self.key_close) else {
1002                break;
1003            };
1004            let key = raw[ks..ks + ke].trim().to_string();
1005            let vs = ks + ke + self.key_close.len();
1006            if self.val_close.is_empty() {
1007                break;
1008            }
1009            let Some(ve) = raw[vs..].find(&self.val_close) else {
1010                break;
1011            };
1012            if !key.is_empty() {
1013                let v = self.value(&name, &key, &raw[vs..vs + ve], schemas);
1014                out.push((key, v));
1015            }
1016            *cursor = vs + ve;
1017        }
1018        out
1019    }
1020}
1021
1022pub fn parse_gemma_tool_call(raw: &str) -> Option<(String, String)> {
1023    let mut body = raw.trim();
1024    if let Some(i) = body.find(GEMMA_TOOL_OPEN) {
1025        body = &body[i + GEMMA_TOOL_OPEN.len()..];
1026    }
1027    if let Some(i) = body.find(GEMMA_TOOL_CLOSE) {
1028        body = &body[..i];
1029    }
1030    let rest = body.trim().strip_prefix("call:")?;
1031    let brace = rest.find('{')?;
1032    let name = rest[..brace].trim().to_string();
1033    if name.is_empty() {
1034        return None;
1035    }
1036    let cs: Vec<char> = rest.chars().collect();
1037    let mut p = rest[..brace].chars().count();
1038    let v = gemma_parse_value(&cs, &mut p, false);
1039    if v.is_null() {
1040        return Some((name, "{}".to_string()));
1041    }
1042    Some((name, serde_json::to_string(&v).ok()?))
1043}
1044
1045// Re-serialised as the HTTP routes do, so a tool declared natively renders the tokens the same
1046// tool sent to /v1/chat/completions does. A bare function or an Anthropic tool is wrapped the
1047// way the Anthropic route wraps it.
1048pub fn function_tool(json: &str) -> Result<String, String> {
1049    use serde_json::{json, Value};
1050    let Value::Object(mut tool) =
1051        serde_json::from_str::<Value>(json).map_err(|e| format!("not JSON: {e}"))?
1052    else {
1053        return Err("not a JSON object".into());
1054    };
1055    let named = |f: &serde_json::Map<String, Value>| -> Result<(), String> {
1056        match f.get("name").and_then(Value::as_str) {
1057            Some(n) if !n.is_empty() => {}
1058            _ => return Err("it has no name".into()),
1059        }
1060        match f.get("parameters") {
1061            None | Some(Value::Object(_)) => Ok(()),
1062            Some(_) => Err("its parameters are not a JSON schema object".into()),
1063        }
1064    };
1065    if tool.get("type").is_some_and(|t| t != "function") {
1066        return Err("its type is not \"function\"".into());
1067    }
1068    if let Some(f) = tool.get("function") {
1069        let Value::Object(f) = f else {
1070            return Err("its function is not an object".into());
1071        };
1072        named(f)?;
1073        return Ok(Value::Object(tool).to_string());
1074    }
1075    tool.remove("type");
1076    let function = match tool.remove("input_schema") {
1077        Some(schema) => json!({
1078            "name": tool.get("name").cloned().unwrap_or(Value::Null),
1079            "description": tool.get("description").and_then(Value::as_str).unwrap_or_default(),
1080            "parameters": schema,
1081        }),
1082        None => Value::Object(tool),
1083    };
1084    named(function.as_object().expect("built as an object"))?;
1085    Ok(json!({"type": "function", "function": function}).to_string())
1086}
1087
1088#[derive(Debug, Clone, Default)]
1089pub struct ToolSchemas(std::collections::HashMap<String, ToolSchema>);
1090
1091#[derive(Debug, Clone)]
1092struct ToolSchema {
1093    root: serde_json::Value,
1094    args: serde_json::Value,
1095}
1096
1097impl ToolSchemas {
1098    pub fn from_values<'a>(tools: impl IntoIterator<Item = &'a serde_json::Value>) -> ToolSchemas {
1099        let mut m = std::collections::HashMap::new();
1100        for t in tools {
1101            let f = t.get("function").unwrap_or(t);
1102            let Some(name) = f.get("name").and_then(|n| n.as_str()) else { continue };
1103            let params = f
1104                .get("parameters")
1105                .or_else(|| f.get("input_schema"))
1106                .cloned()
1107                .unwrap_or(serde_json::Value::Null);
1108            let args = resolve_local_ref(&params, &params).into_owned();
1109            m.insert(name.to_string(), ToolSchema { root: params, args });
1110        }
1111        ToolSchemas(m)
1112    }
1113
1114    pub fn from_jsons(tools: &[String]) -> ToolSchemas {
1115        let vals: Vec<serde_json::Value> = tools.iter().filter_map(|t| serde_json::from_str(t).ok()).collect();
1116        ToolSchemas::from_values(vals.iter())
1117    }
1118
1119    pub fn of_values(tools: &[serde_json::Value]) -> Option<std::sync::Arc<ToolSchemas>> {
1120        (!tools.is_empty()).then(|| std::sync::Arc::new(ToolSchemas::from_values(tools.iter())))
1121    }
1122
1123    pub fn params(&self, tool: &str) -> Option<&serde_json::Value> {
1124        self.0.get(tool).map(|t| &t.root)
1125    }
1126
1127    pub fn args_schema(&self, tool: &str) -> Option<&serde_json::Value> {
1128        self.0.get(tool).map(|t| &t.args)
1129    }
1130
1131    pub fn property(&self, tool: &str, key: &str) -> Option<&serde_json::Value> {
1132        self.args_schema(tool)?.get("properties")?.get(key)
1133    }
1134
1135    pub fn is_empty(&self) -> bool {
1136        self.0.is_empty()
1137    }
1138}
1139
1140fn resolve_local_ref<'a>(p: &'a serde_json::Value, root: &'a serde_json::Value) -> std::borrow::Cow<'a, serde_json::Value> {
1141    use std::borrow::Cow;
1142    let mut cur: Cow<'a, serde_json::Value> = Cow::Borrowed(p);
1143    for _ in 0..16 {
1144        let Some(r) = cur.get("$ref").and_then(|r| r.as_str()) else { break };
1145        let Some(target) = r.strip_prefix('#').and_then(|ptr| root.pointer(ptr)) else { break };
1146        let siblings: Vec<(String, serde_json::Value)> = cur
1147            .as_object()
1148            .map(|o| o.iter().filter(|(k, _)| *k != "$ref").map(|(k, v)| (k.clone(), v.clone())).collect())
1149            .unwrap_or_default();
1150        if siblings.is_empty() {
1151            cur = Cow::Borrowed(target);
1152            continue;
1153        }
1154        let mut merged = target.clone();
1155        if let Some(obj) = merged.as_object_mut() {
1156            for (k, v) in siblings {
1157                match (k.as_str(), obj.get_mut(&k), v) {
1158                    ("required", Some(serde_json::Value::Array(have)), serde_json::Value::Array(add)) => {
1159                        for x in add {
1160                            if !have.contains(&x) {
1161                                have.push(x);
1162                            }
1163                        }
1164                    }
1165                    ("properties", Some(serde_json::Value::Object(have)), serde_json::Value::Object(add)) => {
1166                        have.extend(add);
1167                    }
1168                    (_, _, v) => {
1169                        obj.insert(k, v);
1170                    }
1171                }
1172            }
1173        }
1174        cur = Cow::Owned(merged);
1175    }
1176    cur
1177}
1178
1179fn static_type_name(t: &str) -> Option<&'static str> {
1180    ["null", "boolean", "integer", "number", "string", "array", "object"].into_iter().find(|n| *n == t)
1181}
1182
1183fn json_type_name(v: &serde_json::Value) -> &'static str {
1184    match v {
1185        serde_json::Value::Null => "null",
1186        serde_json::Value::Bool(_) => "boolean",
1187        serde_json::Value::Number(n) if n.is_i64() || n.is_u64() => "integer",
1188        serde_json::Value::Number(_) => "number",
1189        serde_json::Value::String(_) => "string",
1190        serde_json::Value::Array(_) => "array",
1191        serde_json::Value::Object(_) => "object",
1192    }
1193}
1194
1195fn schema_types(p: &serde_json::Value, root: &serde_json::Value) -> Vec<&'static str> {
1196    fn walk(p: &serde_json::Value, root: &serde_json::Value, depth: u32, out: &mut Vec<&'static str>) {
1197        if depth > 16 {
1198            return;
1199        }
1200        let p = resolve_local_ref(p, root);
1201        match p.get("type") {
1202            Some(serde_json::Value::String(t)) => out.extend(static_type_name(t)),
1203            Some(serde_json::Value::Array(ts)) => {
1204                out.extend(ts.iter().filter_map(|t| t.as_str()).filter_map(static_type_name))
1205            }
1206            _ => {}
1207        }
1208        for k in ["anyOf", "oneOf", "allOf"] {
1209            if let Some(alts) = p.get(k).and_then(|a| a.as_array()) {
1210                for a in alts {
1211                    walk(a, root, depth + 1, out);
1212                }
1213            }
1214        }
1215        if let Some(vals) = p.get("enum").and_then(|e| e.as_array()) {
1216            for v in vals {
1217                out.push(json_type_name(v));
1218            }
1219        }
1220        if let Some(c) = p.get("const") {
1221            out.push(json_type_name(c));
1222        }
1223    }
1224    let mut out = Vec::new();
1225    walk(p, root, 0, &mut out);
1226    out
1227}
1228
1229fn json_is_one_of(v: &serde_json::Value, types: &[&str]) -> bool {
1230    use serde_json::Value as J;
1231    types.iter().any(|t| match (*t, v) {
1232        ("null", J::Null) | ("boolean", J::Bool(_)) | ("array", J::Array(_)) | ("object", J::Object(_)) => true,
1233        ("number", J::Number(_)) => true,
1234        ("integer", J::Number(n)) => n.is_i64() || n.is_u64() || n.as_f64().is_some_and(|f| f.fract() == 0.0),
1235        _ => false,
1236    })
1237}
1238
1239#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1240enum StringUnion {
1241    Typed,
1242    #[cfg_attr(not(any(feature = "basert", test)), allow(dead_code))]
1243    Verbatim,
1244}
1245
1246fn declared_value(
1247    raw: &str,
1248    prop: Option<&serde_json::Value>,
1249    root: &serde_json::Value,
1250    union: StringUnion,
1251) -> serde_json::Value {
1252    let types = prop.map(|p| schema_types(p, root)).unwrap_or_default();
1253    if types.is_empty() {
1254        return typed_value(raw);
1255    }
1256    let text = types.contains(&"string");
1257    let padded = raw.len() != raw.trim().len();
1258    if text && (padded || union == StringUnion::Verbatim) {
1259        return serde_json::Value::String(raw.to_string());
1260    }
1261    let t = raw.trim();
1262    let parsed = serde_json::from_str::<serde_json::Value>(t).ok().or_else(|| {
1263        let py = match t {
1264            "True" => serde_json::Value::Bool(true),
1265            "False" => serde_json::Value::Bool(false),
1266            "None" => serde_json::Value::Null,
1267            _ => return None,
1268        };
1269        (!text && json_is_one_of(&py, &types)).then_some(py)
1270    });
1271    match parsed {
1272        Some(j) if !j.is_string() && json_is_one_of(&j, &types) => j,
1273        _ if text => serde_json::Value::String(raw.to_string()),
1274        Some(j) => j,
1275        None => serde_json::Value::String(raw.to_string()),
1276    }
1277}
1278
1279fn glm_value(raw: &str, prop: Option<&serde_json::Value>, root: &serde_json::Value) -> serde_json::Value {
1280    declared_value(raw, prop, root, StringUnion::Typed)
1281}
1282
1283fn glm_bare_name(name: &str) -> bool {
1284    !name.is_empty() && !name.contains(char::is_whitespace) && !name.contains(['{', '}', '<', '>', '"'])
1285}
1286
1287pub fn parse_glm_tool_call(raw: &str, schemas: Option<&ToolSchemas>) -> Option<(String, String)> {
1288    const AK_O: &str = GLM_ARG_KEY_OPEN;
1289    const AK_C: &str = GLM_ARG_KEY_CLOSE;
1290    const AV_O: &str = GLM_ARG_VALUE_OPEN;
1291    const AV_C: &str = GLM_ARG_VALUE_CLOSE;
1292    let mut body = raw;
1293    if let Some(i) = body.find("<tool_call>") {
1294        body = &body[i + "<tool_call>".len()..];
1295    }
1296    if let Some(i) = body.find("</tool_call>") {
1297        body = &body[..i];
1298    }
1299    let first_key = body.find(AK_O);
1300    let name = body[..first_key.unwrap_or(body.len())].trim();
1301    if !glm_bare_name(name) {
1302        return None;
1303    }
1304    let mut args = serde_json::Map::new();
1305    let mut pos = first_key;
1306    while let Some(p) = pos {
1307        let k_start = p + AK_O.len();
1308        let k_end = k_start + body[k_start..].find(AK_C)?;
1309        let key = body[k_start..k_end].trim();
1310        let after_key = k_end + AK_C.len();
1311        let v_open = after_key + body[after_key..].find(AV_O)?;
1312        if !body[after_key..v_open].trim().is_empty() {
1313            return None;
1314        }
1315        let v_start = v_open + AV_O.len();
1316        let v_end = v_start + body[v_start..].find(AV_C)?;
1317        if !key.is_empty() {
1318            let prop = schemas.and_then(|s| s.property(name, key));
1319            let root = schemas.and_then(|s| s.params(name)).unwrap_or(&serde_json::Value::Null);
1320            args.insert(key.to_string(), glm_value(&body[v_start..v_end], prop, root));
1321        }
1322        let after = &body[v_end + AV_C.len()..];
1323        let next = after.trim_start();
1324        if next.is_empty() {
1325            break;
1326        }
1327        if !next.starts_with(AK_O) {
1328            return None;
1329        }
1330        pos = Some(body.len() - next.len());
1331    }
1332    if let Some(required) = schemas
1333        .and_then(|s| s.args_schema(name))
1334        .and_then(|p| p.get("required"))
1335        .and_then(|r| r.as_array())
1336    {
1337        if required.iter().filter_map(|k| k.as_str()).any(|k| !args.contains_key(k)) {
1338            return None;
1339        }
1340    }
1341    Some((name.to_string(), serde_json::Value::Object(args).to_string()))
1342}
1343
1344const GLM_ARG_KEY_OPEN: &str = "<arg_key>";
1345const GLM_ARG_KEY_CLOSE: &str = "</arg_key>";
1346const GLM_ARG_VALUE_OPEN: &str = "<arg_value>";
1347const GLM_ARG_VALUE_CLOSE: &str = "</arg_value>";
1348
1349fn glm_value_format(schema: Option<&serde_json::Value>, params: &serde_json::Value) -> serde_json::Value {
1350    let any_text = serde_json::json!({ "type": "any_text" });
1351    let Some(schema) = schema else { return any_text };
1352    let schema = resolve_local_ref(schema, params);
1353    if let Some(vals) = fixed_values(&schema, params, 0) {
1354        let elems: Vec<serde_json::Value> = vals
1355            .into_iter()
1356            .map(|v| match v {
1357                serde_json::Value::String(s) => serde_json::json!({ "type": "const_string", "value": s }),
1358                other => serde_json::json!({ "type": "const_string", "value": python_json(&other) }),
1359            })
1360            .collect();
1361        return if elems.len() == 1 {
1362            elems.into_iter().next().unwrap_or(any_text)
1363        } else {
1364            serde_json::json!({ "type": "or", "elements": elems })
1365        };
1366    }
1367    let types = schema_types(&schema, params);
1368    if types.is_empty() || types.contains(&"string") {
1369        return any_text;
1370    }
1371    let mut schema = schema.into_owned();
1372    if let Some(obj) = schema.as_object_mut() {
1373        for defs in ["$defs", "definitions"] {
1374            if let (Some(d), false) = (params.get(defs), obj.contains_key(defs)) {
1375                obj.insert(defs.to_string(), d.clone());
1376            }
1377        }
1378    }
1379    serde_json::json!({ "type": "json_schema", "json_schema": schema })
1380}
1381
1382fn fixed_values(schema: &serde_json::Value, root: &serde_json::Value, depth: u32) -> Option<Vec<serde_json::Value>> {
1383    if depth > 16 {
1384        return None;
1385    }
1386    let schema = resolve_local_ref(schema, root);
1387    if let Some(c) = schema.get("const") {
1388        return Some(vec![c.clone()]);
1389    }
1390    if let Some(vals) = schema.get("enum").and_then(|e| e.as_array()).filter(|v| !v.is_empty()) {
1391        return Some(vals.clone());
1392    }
1393    for k in ["anyOf", "oneOf"] {
1394        if let Some(alts) = schema.get(k).and_then(|a| a.as_array()).filter(|a| !a.is_empty()) {
1395            let mut out: Vec<serde_json::Value> = Vec::new();
1396            for a in alts {
1397                for v in fixed_values(a, root, depth + 1)? {
1398                    if !out.contains(&v) {
1399                        out.push(v);
1400                    }
1401                }
1402            }
1403            return Some(out);
1404        }
1405    }
1406    None
1407}
1408
1409fn python_json(v: &serde_json::Value) -> String {
1410    match v {
1411        serde_json::Value::Array(a) => format!("[{}]", a.iter().map(python_json).collect::<Vec<_>>().join(", ")),
1412        serde_json::Value::Object(o) => format!(
1413            "{{{}}}",
1414            o.iter()
1415                .map(|(k, v)| format!("{}: {}", serde_json::Value::String(k.clone()), python_json(v)))
1416                .collect::<Vec<_>>()
1417                .join(", ")
1418        ),
1419        scalar => scalar.to_string(),
1420    }
1421}
1422
1423fn glm_call_body_format(name: &str, params: &serde_json::Value) -> serde_json::Value {
1424    let args = resolve_local_ref(params, params);
1425    let props = args.get("properties").and_then(|p| p.as_object());
1426    let required: Vec<String> = args
1427        .get("required")
1428        .and_then(|r| r.as_array())
1429        .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
1430        .unwrap_or_default();
1431    let arg = |key: &str, schema: Option<&serde_json::Value>| {
1432        serde_json::json!({
1433            "type": "sequence",
1434            "elements": [
1435                { "type": "const_string", "value": format!("{GLM_ARG_KEY_OPEN}{key}{GLM_ARG_KEY_CLOSE}") },
1436                {
1437                    "type": "tag",
1438                    "begin": GLM_ARG_VALUE_OPEN,
1439                    "content": glm_value_format(schema, params),
1440                    "end": GLM_ARG_VALUE_CLOSE,
1441                },
1442            ]
1443        })
1444    };
1445    let mut elements = vec![serde_json::json!({ "type": "const_string", "value": name })];
1446    for (key, schema) in props.into_iter().flatten() {
1447        if required.contains(key) {
1448            elements.push(arg(key, Some(schema)));
1449        } else {
1450            elements.push(serde_json::json!({ "type": "optional", "content": arg(key, Some(schema)) }));
1451        }
1452    }
1453    for key in required.iter().filter(|k| !props.is_some_and(|p| p.contains_key(k.as_str()))) {
1454        elements.push(arg(key, None));
1455    }
1456    serde_json::json!({ "type": "sequence", "elements": elements })
1457}
1458
1459fn wrap_structural_tag(format: serde_json::Value) -> String {
1460    serde_json::json!({ "type": "structural_tag", "format": format }).to_string()
1461}
1462
1463fn stop_after_first(at_least_one: bool) -> bool {
1464    at_least_one
1465}
1466
1467fn value_format(schema: &serde_json::Value, string_pattern: &str) -> serde_json::Value {
1468    if let Some(vals) = schema.get("enum").and_then(|e| e.as_array()) {
1469        let elems: Vec<serde_json::Value> = vals
1470            .iter()
1471            .map(|v| {
1472                let lit = v.as_str().map(|s| s.to_string()).unwrap_or_else(|| v.to_string());
1473                serde_json::json!({ "type": "const_string", "value": lit })
1474            })
1475            .collect();
1476        if !elems.is_empty() {
1477            return serde_json::json!({ "type": "or", "elements": elems });
1478        }
1479    }
1480    match schema.get("type").and_then(|t| t.as_str()).unwrap_or("string") {
1481        "integer" => serde_json::json!({ "type": "regex", "pattern": r"-?[0-9]+" }),
1482        "number" => serde_json::json!({ "type": "regex", "pattern": r"-?[0-9]+(\.[0-9]+)?" }),
1483        "boolean" => serde_json::json!({
1484            "type": "or",
1485            "elements": [
1486                { "type": "const_string", "value": "true" },
1487                { "type": "const_string", "value": "false" },
1488            ]
1489        }),
1490        "array" => serde_json::json!({
1491            "type": "regex",
1492            "pattern": r"\[[^{}\[\]\n]*\]"
1493        }),
1494        "object" => serde_json::json!({ "type": "regex", "pattern": r"\{[^{}\n]*\}" }),
1495        _ => serde_json::json!({ "type": "regex", "pattern": string_pattern }),
1496    }
1497}
1498
1499const GEMMA_STRING: &str = r"[A-Za-z0-9_.:/=@+-][A-Za-z0-9 _.:/=@+-]*";
1500
1501const ATEM_STRING: &str = r"[A-Za-z0-9_.:/=@+,(){}\[\]!?'-][A-Za-z0-9 _.:/=@+,(){}\[\]!?'-]*";
1502
1503fn gemma_value_format(schema: &serde_json::Value) -> serde_json::Value {
1504    value_format(schema, GEMMA_STRING)
1505}
1506
1507fn atem_call_body_format(name: &str, params: &serde_json::Value) -> serde_json::Value {
1508    let props = params.get("properties").and_then(|p| p.as_object());
1509    let required: Vec<String> = params
1510        .get("required")
1511        .and_then(|r| r.as_array())
1512        .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
1513        .unwrap_or_default();
1514    let param = |key: &str| -> serde_json::Value {
1515        let schema = props
1516            .and_then(|p| p.get(key))
1517            .cloned()
1518            .unwrap_or_else(|| serde_json::json!({ "type": "string" }));
1519        serde_json::json!({
1520            "type": "sequence",
1521            "elements": [
1522                { "type": "const_string", "value": format!("<atem:parameter name=\"{key}\">") },
1523                value_format(&schema, ATEM_STRING),
1524                { "type": "const_string", "value": "</atem:parameter>\n" },
1525            ]
1526        })
1527    };
1528    let all: Vec<String> = props.map(|p| p.keys().cloned().collect()).unwrap_or_default();
1529
1530    let mut elements = vec![serde_json::json!({
1531        "type": "const_string",
1532        "value": format!("\n<atem:invoke name=\"{name}\">\n"),
1533    })];
1534    for key in &all {
1535        let e = param(key);
1536        if required.contains(key) {
1537            elements.push(e);
1538        } else {
1539            elements.push(serde_json::json!({ "type": "optional", "content": e }));
1540        }
1541    }
1542    elements.push(serde_json::json!({ "type": "const_string", "value": "</atem:invoke>\n" }));
1543    serde_json::json!({ "type": "sequence", "elements": elements })
1544}
1545
1546fn gemma_call_body_format(name: &str, params: &serde_json::Value) -> serde_json::Value {
1547    let props = params.get("properties").and_then(|p| p.as_object());
1548    let required: Vec<String> = params
1549        .get("required")
1550        .and_then(|r| r.as_array())
1551        .map(|a| a.iter().filter_map(|v| v.as_str().map(String::from)).collect())
1552        .unwrap_or_default();
1553
1554    let pair = |key: &str| -> serde_json::Value {
1555        let schema = props
1556            .and_then(|p| p.get(key))
1557            .cloned()
1558            .unwrap_or_else(|| serde_json::json!({ "type": "string" }));
1559        serde_json::json!({
1560            "type": "sequence",
1561            "elements": [
1562                { "type": "const_string", "value": format!("{key}:") },
1563                gemma_value_format(&schema),
1564            ]
1565        })
1566    };
1567
1568    let mut all: Vec<String> = props.map(|p| p.keys().cloned().collect()).unwrap_or_default();
1569    all.sort();
1570
1571    let mut elements = vec![serde_json::json!({
1572        "type": "const_string",
1573        "value": format!("call:{name}{{"),
1574    })];
1575    let mut first = true;
1576    for key in all.iter().filter(|k| required.contains(k)) {
1577        if !first {
1578            elements.push(serde_json::json!({ "type": "const_string", "value": "," }));
1579        }
1580        first = false;
1581        elements.push(pair(key));
1582    }
1583    elements.push(serde_json::json!({ "type": "const_string", "value": "}" }));
1584    serde_json::json!({ "type": "sequence", "elements": elements })
1585}
1586
1587pub const GEMMA_TOOL_OPEN: &str = "<|tool_call>";
1588pub const GEMMA_TOOL_CLOSE: &str = "<tool_call|>";
1589
1590pub const GEMMA_TOOL_RESPONSE_OPEN: &str = "<|tool_response>";
1591pub const GEMMA_TOOL_RESPONSE_CLOSE: &str = "<tool_response|>";
1592
1593#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1594pub enum ToolEnvelope {
1595    #[default]
1596    Json,
1597    Atem,
1598    Harmony,
1599    Gemma,
1600    Glm,
1601}
1602
1603impl ToolEnvelope {
1604    pub fn from_name(s: &str) -> Option<ToolEnvelope> {
1605        match s {
1606            "json" => Some(ToolEnvelope::Json),
1607            "atem" => Some(ToolEnvelope::Atem),
1608            "gemma" => Some(ToolEnvelope::Gemma),
1609            "harmony" => Some(ToolEnvelope::Harmony),
1610            "glm" => Some(ToolEnvelope::Glm),
1611            _ => None,
1612        }
1613    }
1614
1615    pub fn names() -> &'static [&'static str] {
1616        &["json", "atem", "gemma", "harmony", "glm"]
1617    }
1618
1619    pub fn name(&self) -> &'static str {
1620        match self {
1621            ToolEnvelope::Json => "json",
1622            ToolEnvelope::Atem => "atem",
1623            ToolEnvelope::Gemma => "gemma",
1624            ToolEnvelope::Harmony => "harmony",
1625            ToolEnvelope::Glm => "glm",
1626        }
1627    }
1628
1629    pub fn parse_call(&self, raw: &str, schemas: Option<&ToolSchemas>) -> Option<(String, String)> {
1630        match self {
1631            ToolEnvelope::Json => parse_json_tool_call(raw),
1632            ToolEnvelope::Atem => crate::atem::parse_atem_call(raw),
1633            ToolEnvelope::Harmony => crate::harmony::parse_harmony_tool_call(raw),
1634            ToolEnvelope::Gemma => parse_gemma_tool_call(raw),
1635            ToolEnvelope::Glm => parse_glm_tool_call(raw, schemas),
1636        }
1637    }
1638
1639    pub fn named_call(&self, raw: &str) -> Option<(String, String)> {
1640        match self {
1641            ToolEnvelope::Harmony => crate::harmony::named_call(raw),
1642            ToolEnvelope::Json | ToolEnvelope::Atem | ToolEnvelope::Gemma | ToolEnvelope::Glm => None,
1643        }
1644    }
1645
1646    pub fn structural_tag(
1647        &self,
1648        tool_jsons: &[String],
1649        begin: &str,
1650        end: &str,
1651        at_least_one: bool,
1652    ) -> Option<String> {
1653        if tool_jsons.is_empty() || begin.is_empty() || end.is_empty() {
1654            return None;
1655        }
1656        match self {
1657            ToolEnvelope::Harmony => None,
1658            ToolEnvelope::Atem => {
1659                let mut tags = Vec::new();
1660                for t in tool_jsons {
1661                    let v: serde_json::Value = serde_json::from_str(t).ok()?;
1662                    let f = v.get("function").unwrap_or(&v);
1663                    let name = f.get("name").and_then(|n| n.as_str())?;
1664                    let params = f
1665                        .get("parameters")
1666                        .cloned()
1667                        .unwrap_or_else(|| serde_json::json!({ "type": "object" }));
1668                    tags.push(serde_json::json!({
1669                        "type": "tag",
1670                        "begin": begin,
1671                        "content": atem_call_body_format(name, &params),
1672                        "end": end,
1673                    }));
1674                }
1675                Some(wrap_structural_tag(serde_json::json!({
1676                    "type": "triggered_tags",
1677                    "triggers": [begin],
1678                    "tags": tags,
1679                    "at_least_one": at_least_one,
1680                    "stop_after_first": stop_after_first(at_least_one),
1681                })))
1682            }
1683            ToolEnvelope::Glm => {
1684                if !at_least_one {
1685                    return None;
1686                }
1687                let mut tags = Vec::new();
1688                for t in tool_jsons {
1689                    let v: serde_json::Value = serde_json::from_str(t).ok()?;
1690                    let f = v.get("function").unwrap_or(&v);
1691                    let name = f.get("name").and_then(|n| n.as_str())?;
1692                    let params = f
1693                        .get("parameters")
1694                        .cloned()
1695                        .unwrap_or_else(|| serde_json::json!({ "type": "object" }));
1696                    tags.push(serde_json::json!({
1697                        "type": "tag",
1698                        "begin": begin,
1699                        "content": glm_call_body_format(name, &params),
1700                        "end": end,
1701                    }));
1702                }
1703                Some(wrap_structural_tag(serde_json::json!({
1704                    "type": "triggered_tags",
1705                    "triggers": [begin],
1706                    "tags": tags,
1707                    "at_least_one": at_least_one,
1708                    "stop_after_first": stop_after_first(at_least_one),
1709                })))
1710            }
1711            ToolEnvelope::Gemma => {
1712                if !at_least_one {
1713                    return None;
1714                }
1715                let mut tags = Vec::new();
1716                for t in tool_jsons {
1717                    let v: serde_json::Value = serde_json::from_str(t).ok()?;
1718                    let f = v.get("function").unwrap_or(&v);
1719                    let name = f.get("name").and_then(|n| n.as_str())?;
1720                    let params = f
1721                        .get("parameters")
1722                        .cloned()
1723                        .unwrap_or_else(|| serde_json::json!({ "type": "object" }));
1724                    tags.push(serde_json::json!({
1725                        "type": "tag",
1726                        "begin": begin,
1727                        "content": gemma_call_body_format(name, &params),
1728                        "end": end,
1729                    }));
1730                }
1731                Some(wrap_structural_tag(serde_json::json!({
1732                    "type": "triggered_tags",
1733                    "triggers": [begin],
1734                    "tags": tags,
1735                    "at_least_one": at_least_one,
1736                    "stop_after_first": stop_after_first(at_least_one),
1737                })))
1738            }
1739            ToolEnvelope::Json => {
1740                let mut tags = Vec::new();
1741                for t in tool_jsons {
1742                    let v: serde_json::Value = serde_json::from_str(t).ok()?;
1743                    let f = v.get("function").unwrap_or(&v);
1744                    let name = f.get("name").and_then(|n| n.as_str())?;
1745                    let params = f
1746                        .get("parameters")
1747                        .cloned()
1748                        .unwrap_or_else(|| serde_json::json!({ "type": "object" }));
1749                    tags.push(serde_json::json!({
1750                        "type": "tag",
1751                        "begin": begin,
1752                        "content": {
1753                            "type": "json_schema",
1754                            "json_schema": {
1755                                "type": "object",
1756                                "properties": {
1757                                    "name": { "const": name },
1758                                    "arguments": params,
1759                                },
1760                                "required": ["name", "arguments"],
1761                                "additionalProperties": false,
1762                            },
1763                        },
1764                        "end": end,
1765                    }));
1766                }
1767                Some(wrap_structural_tag(serde_json::json!({
1768                    "type": "triggered_tags",
1769                    "triggers": [begin],
1770                    "tags": tags,
1771                    "at_least_one": at_least_one,
1772                    "stop_after_first": stop_after_first(at_least_one),
1773                })))
1774            }
1775        }
1776    }
1777
1778    pub fn call_grammar(&self, tool_jsons: &[String]) -> Option<String> {
1779        match self {
1780            ToolEnvelope::Atem => None,
1781            ToolEnvelope::Gemma => None,
1782            ToolEnvelope::Harmony => None,
1783            ToolEnvelope::Glm => None,
1784            ToolEnvelope::Json => {
1785                if tool_jsons.is_empty() {
1786                    return None;
1787                }
1788                let mut variants = Vec::new();
1789                for t in tool_jsons {
1790                    let v: serde_json::Value = serde_json::from_str(t).ok()?;
1791                    let f = v.get("function").unwrap_or(&v);
1792                    let name = f.get("name").and_then(|n| n.as_str())?;
1793                    let params = f
1794                        .get("parameters")
1795                        .cloned()
1796                        .unwrap_or_else(|| serde_json::json!({ "type": "object" }));
1797                    variants.push(serde_json::json!({
1798                        "type": "object",
1799                        "properties": { "name": { "const": name }, "arguments": params },
1800                        "required": ["name", "arguments"],
1801                        "additionalProperties": false,
1802                    }));
1803                }
1804                let schema = if variants.len() == 1 {
1805                    variants.pop().unwrap()
1806                } else {
1807                    serde_json::json!({ "anyOf": variants })
1808                };
1809                Some(schema.to_string())
1810            }
1811        }
1812    }
1813}
1814
1815pub fn tool_envelope_override_conflict(codec: &dyn TextCodec, env: ToolEnvelope) -> Option<String> {
1816    let own = codec.tool_envelope();
1817    if env == own {
1818        return None;
1819    }
1820    if env == ToolEnvelope::Glm
1821        && codec
1822            .chat_template_source()
1823            .is_some_and(|t| t.contains(GLM_ARG_KEY_OPEN) && t.contains(GLM_ARG_VALUE_OPEN))
1824    {
1825        return None;
1826    }
1827    Some(format!(
1828        "--tool-call-parser {} conflicts with this model's own tool wire ({}). The dialect renders \
1829         its instructions from the checkpoint's template, so the override would instruct the model \
1830         in one format while parsing another. Drop the flag to use the derived wire{}.",
1831        env.name(),
1832        own.name(),
1833        if env == ToolEnvelope::Glm {
1834            " (`glm` is accepted only where the chat template itself spells the GLM \
1835             <arg_key>/<arg_value> wire)"
1836        } else {
1837            ""
1838        }
1839    ))
1840}
1841
1842pub struct WithToolEnvelope {
1843    inner: Box<dyn TextCodec + Send + Sync>,
1844    envelope: ToolEnvelope,
1845}
1846
1847impl WithToolEnvelope {
1848    pub fn new(inner: Box<dyn TextCodec + Send + Sync>, envelope: ToolEnvelope) -> Self {
1849        WithToolEnvelope { inner, envelope }
1850    }
1851}
1852
1853impl TextCodec for WithToolEnvelope {
1854    fn tool_envelope(&self) -> ToolEnvelope {
1855        self.envelope
1856    }
1857    fn renders_per_message(&self) -> bool {
1858        self.inner.renders_per_message()
1859    }
1860    fn tool_call_delimiters(&self) -> Option<(String, String)> {
1861        self.inner.tool_call_delimiters()
1862    }
1863    fn marker_literal(&self, token: u32) -> String {
1864        self.inner.marker_literal(token)
1865    }
1866    fn encode(&self, text: &str) -> Vec<u32> {
1867        self.inner.encode(text)
1868    }
1869    fn encode_content(&self, text: &str) -> Vec<u32> {
1870        self.inner.encode_content(text)
1871    }
1872    fn encode_pieces(&self, pieces: &[(&str, bool)]) -> Vec<u32> {
1873        self.inner.encode_pieces(pieces)
1874    }
1875    fn token_bytes(&self, token: u32) -> Vec<u8> {
1876        self.inner.token_bytes(token)
1877    }
1878    fn chat_template_source(&self) -> Option<String> {
1879        self.inner.chat_template_source()
1880    }
1881    fn decode(&self, tokens: &[u32]) -> String {
1882        self.inner.decode(tokens)
1883    }
1884    fn stream_prologue(&self) -> Vec<u32> {
1885        self.inner.stream_prologue()
1886    }
1887    fn behavior_fingerprint(&self) -> Option<superfluid_fingerprint::BehaviorFingerprint> {
1888        self.inner.behavior_fingerprint()
1889    }
1890    fn render_message(&self, role: u32, text: &str) -> Option<Vec<u32>> {
1891        self.inner.render_message(role, text)
1892    }
1893    fn generation_prefix(&self, state: TurnState) -> Option<Vec<u32>> {
1894        self.inner.generation_prefix(state)
1895    }
1896    fn generation_prefix_with(&self, state: TurnState, thinking: Option<bool>) -> Option<Vec<u32>> {
1897        self.inner.generation_prefix_with(state, thinking)
1898    }
1899    fn turn_separator(&self) -> Option<Vec<u32>> {
1900        self.inner.turn_separator()
1901    }
1902    fn channel_markers(&self) -> Vec<(u32, u32, u32)> {
1903        self.inner.channel_markers()
1904    }
1905    fn response_format_tag(&self, json_schema: &str) -> Option<String> {
1906        self.inner.response_format_tag(json_schema)
1907    }
1908    fn render_system_with_tools(
1909        &self,
1910        system: Option<&str>,
1911        tool_jsons: &[String],
1912    ) -> Option<Vec<u32>> {
1913        self.inner.render_system_with_tools(system, tool_jsons)
1914    }
1915    fn render_tool_result(&self, name: &str, content: &str) -> Option<Vec<u32>> {
1916        self.inner.render_tool_result(name, content)
1917    }
1918    fn render_assistant_with_tool_calls(
1919        &self,
1920        content: &str,
1921        calls: &[(String, String)],
1922    ) -> Option<Vec<u32>> {
1923        self.inner.render_assistant_with_tool_calls(content, calls)
1924    }
1925    fn render_assistant_with_tool_calls_after_query(
1926        &self,
1927        content: &str,
1928        calls: &[(String, String)],
1929    ) -> Option<Vec<u32>> {
1930        self.inner.render_assistant_with_tool_calls_after_query(content, calls)
1931    }
1932    fn render_fim(&self, prefix: &str, suffix: &str, mode: u8) -> Option<Vec<u32>> {
1933        self.inner.render_fim(prefix, suffix, mode)
1934    }
1935    fn render_turn_with_tokens(
1936        &self,
1937        role: u32,
1938        pre_text: &str,
1939        inner: &[u32],
1940        post_text: &str,
1941    ) -> Option<Vec<u32>> {
1942        self.inner.render_turn_with_tokens(role, pre_text, inner, post_text)
1943    }
1944    fn render_block(&self, role: u32, kind: u32, payload: &str) -> Option<Vec<u32>> {
1945        self.inner.render_block(role, kind, payload)
1946    }
1947    fn channelizer(&self) -> Box<dyn Channelizer> {
1948        self.inner.channelizer()
1949    }
1950    fn render_prompt(&self, messages: &[(u32, String)], tools: &[String]) -> Option<Vec<u32>> {
1951        self.inner.render_prompt(messages, tools)
1952    }
1953    fn turn_terminators(&self) -> Vec<u32> {
1954        self.inner.turn_terminators()
1955    }
1956    fn render_prompt_structured(
1957        &self,
1958        messages: &[ChatMessage],
1959        tools: &[String],
1960    ) -> Option<Vec<u32>> {
1961        self.inner.render_prompt_structured(messages, tools)
1962    }
1963    fn render_prompt_structured_with(
1964        &self,
1965        messages: &[ChatMessage],
1966        tools: &[String],
1967        kwargs: &serde_json::Map<String, serde_json::Value>,
1968    ) -> Option<Vec<u32>> {
1969        self.inner.render_prompt_structured_with(messages, tools, kwargs)
1970    }
1971    fn template_refusal(
1972        &self,
1973        messages: &[ChatMessage],
1974        tools: &[String],
1975        kwargs: &serde_json::Map<String, serde_json::Value>,
1976    ) -> Option<String> {
1977        self.inner.template_refusal(messages, tools, kwargs)
1978    }
1979    fn render_system_full(
1980        &self,
1981        system: Option<&str>,
1982        tool_jsons: &[String],
1983        kwargs: &serde_json::Map<String, serde_json::Value>,
1984    ) -> Option<Vec<u32>> {
1985        self.inner.render_system_full(system, tool_jsons, kwargs)
1986    }
1987    fn validate_template_kwargs(
1988        &self,
1989        kwargs: &serde_json::Map<String, serde_json::Value>,
1990    ) -> Result<(), String> {
1991        self.inner.validate_template_kwargs(kwargs)
1992    }
1993    fn supports_reasoning_effort(&self) -> bool {
1994        self.inner.supports_reasoning_effort()
1995    }
1996    fn reasoning_effort_levels(&self) -> Vec<&'static str> {
1997        self.inner.reasoning_effort_levels()
1998    }
1999    fn supports_enable_thinking(&self) -> bool {
2000        self.inner.supports_enable_thinking()
2001    }
2002    fn empty_think_tokens(&self) -> Option<Vec<u32>> {
2003        self.inner.empty_think_tokens()
2004    }
2005}
2006
2007pub trait Channelizer: Send {
2008    fn split(&mut self, tokens: &[u32]) -> Vec<Run>;
2009    fn current_channel(&self) -> u32;
2010    fn prime(&mut self, _channel: u32) {}
2011}
2012
2013pub mod fim_mode {
2014    pub const PSM: u8 = 0;
2015    pub const SPM: u8 = 1;
2016}
2017
2018pub const MODEL_VISIBILITY_VERSION: u32 = 2;
2019
2020pub fn project_block_text(kind: u32, payload: &str) -> Option<String> {
2021    use crate::wal::block_kind;
2022    let v: serde_json::Value = serde_json::from_str(payload).unwrap_or(serde_json::Value::Null);
2023    let str_of = |k: &str| v.get(k).and_then(|x| x.as_str()).unwrap_or("").to_string();
2024    let cap = |mut t: String, max: usize| {
2025        if t.len() > max {
2026            let mut cut = max;
2027            while !t.is_char_boundary(cut) {
2028                cut -= 1;
2029            }
2030            t.truncate(cut);
2031            t.push_str("\n…[truncated]");
2032        }
2033        t
2034    };
2035    Some(match kind {
2036        block_kind::FILE_DIFF => {
2037            let path = str_of("path");
2038            let diff = str_of("diff");
2039            cap(format!("```diff\n# {path}\n{diff}\n```"), 16 * 1024)
2040        }
2041        block_kind::ARTIFACT => {
2042            let title = str_of("title");
2043            let content = str_of("content");
2044            cap(format!("[artifact: {title}]\n{content}"), 16 * 1024)
2045        }
2046        block_kind::CITATION => {
2047            let title = str_of("title");
2048            let url = str_of("url");
2049            let quote = str_of("quote");
2050            if quote.is_empty() {
2051                format!("[citation] {title} — {url}")
2052            } else {
2053                format!("[citation] {title} — {url}\n> {quote}")
2054            }
2055        }
2056        block_kind::PROGRESS => String::new(),
2057        block_kind::DIAGNOSTIC => {
2058            let severity = str_of("severity");
2059            let message = str_of("message");
2060            let path = str_of("path");
2061            let line = v.get("line").and_then(|x| x.as_u64()).unwrap_or(0);
2062            if path.is_empty() {
2063                format!("[diagnostic {severity}] {message}")
2064            } else {
2065                format!("[diagnostic {severity}] {path}:{line}: {message}")
2066            }
2067        }
2068        block_kind::WORKSPACE_REF => format!("@{}", str_of("path")),
2069        block_kind::IMAGE => String::new(),
2070        _ => return None,
2071    })
2072}
2073
2074pub struct Segmenter {
2075    pairs: Vec<(u32, u32, u32)>,
2076    active: Option<usize>,
2077    names: Vec<(u32, Vec<u32>)>,
2078    name_at: Option<usize>,
2079}
2080
2081impl Segmenter {
2082    pub fn new(pairs: Vec<(u32, u32, u32)>) -> Segmenter {
2083        Segmenter {
2084            pairs,
2085            active: None,
2086            names: Vec::new(),
2087            name_at: None,
2088        }
2089    }
2090
2091    pub fn with_names(mut self, names: Vec<(u32, Vec<u32>)>) -> Segmenter {
2092        self.names = names;
2093        self
2094    }
2095
2096    fn name_of(&self, open: u32) -> Option<&[u32]> {
2097        self.names.iter().find(|(o, _)| *o == open).map(|(_, n)| n.as_slice())
2098    }
2099
2100    fn takes_name(&mut self, open: u32, t: u32) -> bool {
2101        let (Some(k), Some(name)) = (self.name_at, self.name_of(open)) else {
2102            return false;
2103        };
2104        let named = name.get(k) == Some(&t);
2105        let len = name.len();
2106        self.name_at = (named && k + 1 < len).then_some(k + 1);
2107        named
2108    }
2109
2110    fn is_marker(&self, t: u32) -> bool {
2111        self.pairs.iter().any(|&(o, c, _)| o == t || c == t)
2112    }
2113}
2114
2115impl Segmenter {
2116    fn pair_for(&self, channel: u32) -> Option<usize> {
2117        self.pairs.iter().position(|&(_, _, c)| c == channel)
2118    }
2119}
2120
2121impl Channelizer for Segmenter {
2122    fn split(&mut self, tokens: &[u32]) -> Vec<Run> {
2123        if self.pairs.is_empty() {
2124            return vec![Run {
2125                span: tokens.to_vec(),
2126                text: tokens.to_vec(),
2127                channel: crate::wal::channel::TEXT,
2128                closes: false,
2129            }];
2130        }
2131        let mut out: Vec<Run> = Vec::new();
2132        let mut run: Vec<u32> = Vec::new();
2133        let mut text: Vec<u32> = Vec::new();
2134        let mut run_channel = self.current_channel();
2135        fn flush(
2136            run: &mut Vec<u32>,
2137            text: &mut Vec<u32>,
2138            ch: u32,
2139            closes: bool,
2140            out: &mut Vec<Run>,
2141        ) {
2142            if !run.is_empty() {
2143                out.push(Run {
2144                    span: std::mem::take(run),
2145                    text: std::mem::take(text),
2146                    channel: ch,
2147                    closes,
2148                });
2149            }
2150        }
2151        for &t in tokens {
2152            match self.active {
2153                None => {
2154                    if let Some(i) = self.pairs.iter().position(|&(open, _, _)| open == t) {
2155                        flush(&mut run, &mut text, run_channel, false, &mut out);
2156                        self.active = Some(i);
2157                        self.name_at = self.name_of(t).map(|_| 0);
2158                        run_channel = self.pairs[i].2;
2159                        run.push(t);
2160                    } else {
2161                        run.push(t);
2162                        if !self.is_marker(t) {
2163                            text.push(t);
2164                        }
2165                    }
2166                }
2167                Some(i) => {
2168                    if self.pairs[i].1 == t {
2169                        run.push(t);
2170                        flush(&mut run, &mut text, run_channel, true, &mut out);
2171                        self.active = None;
2172                        self.name_at = None;
2173                        run_channel = crate::wal::channel::TEXT;
2174                    } else {
2175                        run.push(t);
2176                        let named = self.takes_name(self.pairs[i].0, t);
2177                        if !named && !self.is_marker(t) {
2178                            text.push(t);
2179                        }
2180                    }
2181                }
2182            }
2183        }
2184        flush(&mut run, &mut text, run_channel, false, &mut out);
2185        out
2186    }
2187
2188    fn current_channel(&self) -> u32 {
2189        self.active
2190            .map(|i| self.pairs[i].2)
2191            .unwrap_or(crate::wal::channel::TEXT)
2192    }
2193
2194    fn prime(&mut self, channel: u32) {
2195        if let Some(i) = self.pair_for(channel) {
2196            self.active = Some(i);
2197            self.name_at = None;
2198        }
2199    }
2200}
2201
2202pub struct MockCodec;
2203
2204const MOCK_BASE: u32 = 0x100;
2205
2206impl TextCodec for MockCodec {
2207    fn encode(&self, text: &str) -> Vec<u32> {
2208        text.bytes().map(|b| MOCK_BASE + b as u32).collect()
2209    }
2210
2211    fn token_bytes(&self, token: u32) -> Vec<u8> {
2212        if (MOCK_BASE..MOCK_BASE + 256).contains(&token) {
2213            vec![(token - MOCK_BASE) as u8]
2214        } else {
2215            Vec::new()
2216        }
2217    }
2218}
2219
2220pub struct MockChatCodec;
2221
2222pub const MOCK_TURN_END: u32 = MOCK_BASE + 0x1E;
2223pub const MOCK_THINK_OPEN: u32 = MOCK_BASE + 0x02;
2224pub const MOCK_THINK_CLOSE: u32 = MOCK_BASE + 0x03;
2225pub const MOCK_TOOL_OPEN: u32 = MOCK_BASE + 0x04;
2226pub const MOCK_TOOL_CLOSE: u32 = MOCK_BASE + 0x05;
2227
2228impl TextCodec for MockChatCodec {
2229    fn encode(&self, text: &str) -> Vec<u32> {
2230        MockCodec.encode(text)
2231    }
2232    fn encode_content(&self, text: &str) -> Vec<u32> {
2233        MockCodec
2234            .encode(text)
2235            .into_iter()
2236            .map(|t| {
2237                if [MOCK_TURN_END, MOCK_THINK_OPEN, MOCK_THINK_CLOSE, MOCK_TOOL_OPEN, MOCK_TOOL_CLOSE]
2238                    .contains(&t)
2239                {
2240                    MOCK_BASE + b'?' as u32
2241                } else {
2242                    t
2243                }
2244            })
2245            .collect()
2246    }
2247    fn token_bytes(&self, token: u32) -> Vec<u8> {
2248        MockCodec.token_bytes(token)
2249    }
2250    fn behavior_fingerprint(&self) -> Option<superfluid_fingerprint::BehaviorFingerprint> {
2251        Some(superfluid_fingerprint::BehaviorFingerprint {
2252            codec_id: "mock-chat".into(),
2253            codec_version: 2,
2254            tokenizer_hash: superfluid_fingerprint::vocab_hash(2 * MOCK_BASE, |t| {
2255                MockCodec.token_bytes(t)
2256            }),
2257            marker_digest: superfluid_fingerprint::BehaviorFingerprint::marker_digest_of(
2258                &self.channel_markers(),
2259            ),
2260            sampling_impl_version: 1,
2261            block_projection_version: MODEL_VISIBILITY_VERSION,
2262            grammar_version: 0,
2263            speculation: None,
2264        })
2265    }
2266    fn render_message(&self, role: u32, text: &str) -> Option<Vec<u32>> {
2267        let mut span = MockCodec.encode(&format!("<{role}>"));
2268        span.extend(self.encode_content(text));
2269        span.push(MOCK_TURN_END);
2270        Some(span)
2271    }
2272    fn render_turn_with_tokens(
2273        &self,
2274        role: u32,
2275        pre_text: &str,
2276        inner: &[u32],
2277        post_text: &str,
2278    ) -> Option<Vec<u32>> {
2279        let mut span = MockCodec.encode(&format!("<{role}>"));
2280        span.extend(self.encode_content(pre_text));
2281        span.extend_from_slice(inner);
2282        span.extend(self.encode_content(post_text));
2283        span.push(MOCK_TURN_END);
2284        Some(span)
2285    }
2286    fn render_fim(&self, prefix: &str, suffix: &str, mode: u8) -> Option<Vec<u32>> {
2287        const FIM_PRE: u32 = 4_000_000;
2288        const FIM_SUF: u32 = 4_000_001;
2289        const FIM_MID: u32 = 4_000_002;
2290        let mut span = Vec::new();
2291        if mode == fim_mode::SPM {
2292            span.extend([FIM_PRE, FIM_SUF]);
2293            span.extend(MockCodec.encode(suffix));
2294            span.push(FIM_MID);
2295            span.extend(MockCodec.encode(prefix));
2296        } else {
2297            span.push(FIM_PRE);
2298            span.extend(MockCodec.encode(prefix));
2299            span.push(FIM_SUF);
2300            span.extend(MockCodec.encode(suffix));
2301            span.push(FIM_MID);
2302        }
2303        Some(span)
2304    }
2305    fn generation_prefix(&self, state: TurnState) -> Option<Vec<u32>> {
2306        match state {
2307            TurnState::AfterInput => Some(MockCodec.encode("<a>")),
2308            TurnState::MidTurn => None,
2309            TurnState::AfterFinishedTurn => {
2310                let mut s = vec![MOCK_TURN_END];
2311                s.extend(MockCodec.encode("<a>"));
2312                Some(s)
2313            }
2314        }
2315    }
2316    fn channel_markers(&self) -> Vec<(u32, u32, u32)> {
2317        vec![
2318            (
2319                MOCK_THINK_OPEN,
2320                MOCK_THINK_CLOSE,
2321                crate::wal::channel::REASONING,
2322            ),
2323            (
2324                MOCK_TOOL_OPEN,
2325                MOCK_TOOL_CLOSE,
2326                crate::wal::channel::TOOL_CALL,
2327            ),
2328        ]
2329    }
2330    fn render_system_with_tools(
2331        &self,
2332        system: Option<&str>,
2333        tool_jsons: &[String],
2334    ) -> Option<Vec<u32>> {
2335        let mut span = MockCodec.encode("<0>");
2336        span.extend(self.encode_content(system.unwrap_or("")));
2337        for t in tool_jsons {
2338            span.extend(MockCodec.encode(t));
2339        }
2340        span.push(MOCK_TURN_END);
2341        Some(span)
2342    }
2343    fn render_tool_result(&self, _name: &str, content: &str) -> Option<Vec<u32>> {
2344        let mut span = MockCodec.encode("<r>");
2345        span.extend(self.encode_content(content));
2346        span.push(MOCK_TURN_END);
2347        Some(span)
2348    }
2349    fn render_assistant_with_tool_calls(
2350        &self,
2351        content: &str,
2352        calls: &[(String, String)],
2353    ) -> Option<Vec<u32>> {
2354        let mut span = MockCodec.encode("<a>");
2355        span.extend(self.encode_content(content));
2356        for (name, args) in calls {
2357            span.push(MOCK_TOOL_OPEN);
2358            span.extend(MockCodec.encode(&format!(
2359                "{{\"name\": \"{name}\", \"arguments\": {args}}}"
2360            )));
2361            span.push(MOCK_TOOL_CLOSE);
2362        }
2363        span.push(MOCK_TURN_END);
2364        Some(span)
2365    }
2366}
2367
2368pub struct MockTemplateCodec;
2369
2370pub const MOCK_IMAGE_MARKER: u32 = 999;
2371
2372impl TextCodec for MockTemplateCodec {
2373    fn encode(&self, text: &str) -> Vec<u32> {
2374        MockCodec.encode(text)
2375    }
2376    fn decode(&self, tokens: &[u32]) -> String {
2377        MockCodec.decode(tokens)
2378    }
2379    fn token_bytes(&self, token: u32) -> Vec<u8> {
2380        MockCodec.token_bytes(token)
2381    }
2382    fn renders_per_message(&self) -> bool {
2383        false
2384    }
2385    fn render_prompt_structured_with(
2386        &self,
2387        messages: &[ChatMessage],
2388        tools: &[String],
2389        _kwargs: &serde_json::Map<String, serde_json::Value>,
2390    ) -> Option<Vec<u32>> {
2391        let mut span = Vec::new();
2392        for m in messages {
2393            span.extend(MockCodec.encode(&format!("<{}>", m.role)));
2394            if m.parts.is_empty() {
2395                span.extend(MockCodec.encode(&m.content));
2396            } else {
2397                for p in &m.parts {
2398                    match p {
2399                        ContentPart::Text(t) => span.extend(MockCodec.encode(t)),
2400                        ContentPart::Image { .. } => span.push(MOCK_IMAGE_MARKER),
2401                        ContentPart::Audio { .. } => span.push(MOCK_IMAGE_MARKER),
2402                    }
2403                }
2404            }
2405            span.push(MOCK_TURN_END);
2406        }
2407        for t in tools {
2408            span.extend(MockCodec.encode(t));
2409        }
2410        span.extend(MockCodec.encode("<a>"));
2411        Some(span)
2412    }
2413}
2414
2415pub struct MockThinkingTemplateCodec;
2416
2417impl TextCodec for MockThinkingTemplateCodec {
2418    fn encode(&self, text: &str) -> Vec<u32> {
2419        MockCodec.encode(text)
2420    }
2421    fn decode(&self, tokens: &[u32]) -> String {
2422        MockCodec.decode(tokens)
2423    }
2424    fn token_bytes(&self, token: u32) -> Vec<u8> {
2425        MockCodec.token_bytes(token)
2426    }
2427    fn renders_per_message(&self) -> bool {
2428        false
2429    }
2430    fn channel_markers(&self) -> Vec<(u32, u32, u32)> {
2431        MockChatCodec.channel_markers()
2432    }
2433    fn render_prompt_structured_with(
2434        &self,
2435        messages: &[ChatMessage],
2436        tools: &[String],
2437        kwargs: &serde_json::Map<String, serde_json::Value>,
2438    ) -> Option<Vec<u32>> {
2439        let mut span = MockTemplateCodec.render_prompt_structured_with(messages, tools, kwargs)?;
2440        span.push(MOCK_THINK_OPEN);
2441        Some(span)
2442    }
2443    fn trailing_generation_prompt(
2444        &self,
2445        _messages: &[ChatMessage],
2446        _tools: &[String],
2447        _kwargs: &serde_json::Map<String, serde_json::Value>,
2448        span: &[u32],
2449    ) -> usize {
2450        let mut opener = MockCodec.encode("<a>");
2451        opener.push(MOCK_THINK_OPEN);
2452        if span.len() > opener.len() && span.ends_with(&opener) {
2453            opener.len()
2454        } else {
2455            0
2456        }
2457    }
2458}
2459
2460pub struct NoCodec;
2461
2462impl TextCodec for NoCodec {
2463    fn encode(&self, _text: &str) -> Vec<u32> {
2464        Vec::new()
2465    }
2466    fn token_bytes(&self, _token: u32) -> Vec<u8> {
2467        Vec::new()
2468    }
2469}
2470
2471#[derive(Default)]
2472pub struct Utf8Stream {
2473    buf: Vec<u8>,
2474}
2475
2476impl Utf8Stream {
2477    pub fn push(&mut self, bytes: &[u8]) -> String {
2478        self.buf.extend_from_slice(bytes);
2479        let mut out = String::new();
2480        loop {
2481            match std::str::from_utf8(&self.buf) {
2482                Ok(s) => {
2483                    out.push_str(s);
2484                    self.buf.clear();
2485                    return out;
2486                }
2487                Err(e) => {
2488                    let valid = e.valid_up_to();
2489                    // SAFETY-free: valid_up_to bounds a checked-valid
2490                    // prefix.
2491                    out.push_str(std::str::from_utf8(&self.buf[..valid]).expect("checked"));
2492                    match e.error_len() {
2493                        Some(n) => {
2494                            out.push('\u{FFFD}');
2495                            self.buf.drain(..valid + n);
2496                        }
2497                        None => {
2498                            self.buf.drain(..valid);
2499                            return out;
2500                        }
2501                    }
2502                }
2503            }
2504        }
2505    }
2506
2507    pub fn flush(&mut self) -> String {
2508        if self.buf.is_empty() {
2509            return String::new();
2510        }
2511        self.buf.clear();
2512        "\u{FFFD}".to_string()
2513    }
2514}
2515
2516pub struct BundleCodec {
2517    handle: std::sync::Arc<dyn superfluid_engine::Tokenizer>,
2518    vocab_hash: u64,
2519    markers: std::sync::OnceLock<superfluid_engine::Markers>,
2520}
2521
2522impl BundleCodec {
2523    #[cfg(feature = "basert")]
2524    pub fn load(model_path: &std::path::Path) -> Result<BundleCodec, Status> {
2525        let handle =
2526            superfluid_engine_ffi::TokenizerHandle::load(model_path).map_err(|_| Status::Fatal)?;
2527        Ok(Self::from_tokenizer(std::sync::Arc::new(handle)))
2528    }
2529
2530    pub fn from_tokenizer(tok: std::sync::Arc<dyn superfluid_engine::Tokenizer>) -> BundleCodec {
2531        let vocab_hash = superfluid_fingerprint::vocab_hash(tok.vocab_size(), |t| tok.token_bytes(t));
2532        BundleCodec {
2533            handle: tok,
2534            vocab_hash,
2535            markers: std::sync::OnceLock::new(),
2536        }
2537    }
2538
2539    fn markers(&self) -> &superfluid_engine::Markers {
2540        self.markers.get_or_init(|| superfluid_engine::Markers::of(self.handle.as_ref()))
2541    }
2542
2543    pub(crate) fn tokenizer(&self) -> std::sync::Arc<dyn superfluid_engine::Tokenizer> {
2544        std::sync::Arc::clone(&self.handle)
2545    }
2546
2547    pub(crate) fn fingerprint(&self, codec_id: &str, markers: &[(u32, u32, u32)])
2548        -> superfluid_fingerprint::BehaviorFingerprint {
2549        superfluid_fingerprint::BehaviorFingerprint {
2550            codec_id: codec_id.to_string(),
2551            codec_version: 2,
2552            tokenizer_hash: self.vocab_hash,
2553            marker_digest: superfluid_fingerprint::BehaviorFingerprint::marker_digest_of(markers),
2554            sampling_impl_version: 1,
2555            block_projection_version: MODEL_VISIBILITY_VERSION,
2556            grammar_version: 0,
2557            speculation: None,
2558        }
2559    }
2560}
2561
2562impl TextCodec for BundleCodec {
2563    fn encode(&self, text: &str) -> Vec<u32> {
2564        self.handle.encode(text)
2565    }
2566    fn encode_content(&self, text: &str) -> Vec<u32> {
2567        self.handle
2568            .encode_plain(text)
2569            .unwrap_or_else(|| self.markers().encode_plain(self.handle.as_ref(), text))
2570    }
2571    fn encode_pieces(&self, pieces: &[(&str, bool)]) -> Vec<u32> {
2572        self.handle
2573            .encode_pieces(pieces)
2574            .unwrap_or_else(|| self.markers().encode_pieces(self.handle.as_ref(), pieces))
2575    }
2576    fn token_bytes(&self, token: u32) -> Vec<u8> {
2577        self.handle.token_bytes(token)
2578    }
2579    fn marker_literal(&self, token: u32) -> String {
2580        let text = self.decode(&[token]);
2581        if !text.is_empty() {
2582            return text;
2583        }
2584        self.handle
2585            .special_tokens()
2586            .into_iter()
2587            .find(|(_, id)| *id == token)
2588            .map(|(s, _)| s)
2589            .unwrap_or_default()
2590    }
2591    fn behavior_fingerprint(&self) -> Option<superfluid_fingerprint::BehaviorFingerprint> {
2592        Some(self.fingerprint("raw", &[]))
2593    }
2594    fn chat_template_source(&self) -> Option<String> {
2595        let source = self.handle.chat_template_jinja();
2596        (!source.trim().is_empty()).then_some(source)
2597    }
2598    fn render_fim(&self, prefix: &str, suffix: &str, mode: u8) -> Option<Vec<u32>> {
2599        let bos = self.handle.bos_token();
2600        let atomic = |m: &str| -> bool {
2601            let mut t = self.encode(m);
2602            if bos.is_some() && t.first() == bos.as_ref() && t.len() > 1 {
2603                t.remove(0);
2604            }
2605            t.len() == 1
2606        };
2607        const PRE: &str = "<|fim_prefix|>";
2608        const SUF: &str = "<|fim_suffix|>";
2609        const MID: &str = "<|fim_middle|>";
2610        if !(atomic(PRE) && atomic(SUF) && atomic(MID)) {
2611            return None;
2612        }
2613        let pieces: [(&str, bool); 5] = if mode == fim_mode::SPM {
2614            [(PRE, false), (SUF, false), (suffix, true), (MID, false), (prefix, true)]
2615        } else {
2616            [(PRE, false), (prefix, true), (SUF, false), (suffix, true), (MID, false)]
2617        };
2618        Some(self.encode_pieces(&pieces))
2619    }
2620}
2621
2622#[cfg(feature = "basert")]
2623pub fn chatml_codec(model: &std::path::Path) -> Result<ChatMlCodec, &'static str> {
2624    let tok = superfluid_engine_ffi::TokenizerHandle::load(model).map_err(|_| "tokenizer load failed")?;
2625    chatml_codec_from_tokenizer(std::sync::Arc::new(tok))
2626}
2627
2628pub fn chatml_codec_from_tokenizer(
2629    tok: std::sync::Arc<dyn superfluid_engine::Tokenizer>,
2630) -> Result<ChatMlCodec, &'static str> {
2631    let codec = ChatMlCodec::from_tokenizer(std::sync::Arc::clone(&tok))
2632        .map_err(|_| "tokenizer lacks the ChatML markers")?;
2633    if let Ok(t) = crate::template_codec::TemplateCodec::from_tokenizer(tok) {
2634        if !codec.frames_like(&t) {
2635            return Err(
2636                "this model's chat template does not frame turns the way the curated ChatML \
2637                 dialect does, so it would send every turn in a shape the checkpoint was not \
2638                 trained on. Use --dialect auto or --dialect template.",
2639            );
2640        }
2641    }
2642    Ok(codec)
2643}
2644
2645#[cfg(feature = "basert")]
2646pub fn auto_codec(
2647    model: &std::path::Path,
2648) -> Result<(Box<dyn TextCodec + Send + Sync>, &'static str), &'static str> {
2649    let tok = superfluid_engine_ffi::TokenizerHandle::load(model)
2650        .map_err(|_| "no curated codec, no usable chat template, and no raw tokenizer")?;
2651    Ok(auto_codec_from_tokenizer(std::sync::Arc::new(tok)))
2652}
2653
2654pub fn auto_codec_from_tokenizer(
2655    tok: std::sync::Arc<dyn superfluid_engine::Tokenizer>,
2656) -> (Box<dyn TextCodec + Send + Sync>, &'static str) {
2657    let tmpl = crate::template_codec::TemplateCodec::from_tokenizer(std::sync::Arc::clone(&tok)).ok();
2658    let curated = chatml_codec_from_tokenizer(std::sync::Arc::clone(&tok)).ok();
2659    if let Some(c) = curated {
2660        (Box::new(c), "chatml")
2661    } else if let Some(c) = tmpl {
2662        (Box::new(c), "template")
2663    } else {
2664        (Box::new(BundleCodec::from_tokenizer(tok)), "raw")
2665    }
2666}
2667
2668pub(crate) const CONTENT_STAND_IN: &str = "BASERTCONTENTSTANDIN9f2c";
2669
2670pub(crate) fn split_once_exact<'a>(text: &'a str, needle: &str) -> Option<(&'a str, &'a str)> {
2671    let (before, after) = text.split_once(needle)?;
2672    (!after.contains(needle)).then_some((before, after))
2673}
2674
2675pub(crate) fn stand_in_key(n: usize) -> String {
2676    format!("{CONTENT_STAND_IN}{n}Z")
2677}
2678
2679pub(crate) fn stand_in_strings(v: &mut serde_json::Value, subs: &mut Vec<(String, String)>) {
2680    match v {
2681        serde_json::Value::String(text) => {
2682            let key = stand_in_key(subs.len());
2683            subs.push((key.clone(), std::mem::take(text)));
2684            *text = key;
2685        }
2686        serde_json::Value::Array(items) => items.iter_mut().for_each(|i| stand_in_strings(i, subs)),
2687        serde_json::Value::Object(map) => map.values_mut().for_each(|i| stand_in_strings(i, subs)),
2688        _ => {}
2689    }
2690}
2691
2692fn place_known_form(rest: &str, original: &str, follows: impl Fn(&str) -> bool) -> Option<usize> {
2693    let fits = |form: &str| rest.starts_with(form) && follows(&rest[form.len()..]);
2694    if fits(original) {
2695        return Some(original.len());
2696    }
2697    let trimmed = original.trim();
2698    if fits(trimmed) {
2699        return Some(trimmed.len());
2700    }
2701    let quoted = serde_json::to_string(original).unwrap_or_default();
2702    let json = quoted.get(1..quoted.len().saturating_sub(1)).unwrap_or("");
2703    let html_safe = json
2704        .replace('<', "\\u003c")
2705        .replace('>', "\\u003e")
2706        .replace('&', "\\u0026")
2707        .replace('\'', "\\u0027");
2708    if fits(&html_safe) {
2709        return Some(html_safe.len());
2710    }
2711    if fits(json) {
2712        return Some(json.len());
2713    }
2714    None
2715}
2716
2717#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2718pub(crate) enum Placement {
2719    SearchFraming,
2720    KnownFormsOnly,
2721}
2722
2723pub(crate) fn split_around_stand_ins(
2724    stand_ins: &str,
2725    subs: &[(String, String)],
2726    real: &str,
2727    placement: Placement,
2728) -> Option<Vec<(String, bool)>> {
2729    let mut hits: Vec<(usize, usize)> = Vec::with_capacity(subs.len());
2730    for (i, (key, _)) in subs.iter().enumerate() {
2731        let mut it = stand_ins.match_indices(key.as_str());
2732        match (it.next(), it.next()) {
2733            (Some((pos, _)), None) => hits.push((pos, i)),
2734            _ => return None,
2735        }
2736    }
2737    hits.sort_unstable();
2738    let mut pieces: Vec<(String, bool)> = Vec::with_capacity(2 * hits.len() + 1);
2739    let (mut si_at, mut real_at) = (0usize, 0usize);
2740    for (n, (pos, i)) in hits.iter().enumerate() {
2741        let framing = &stand_ins[si_at..*pos];
2742        if !real[real_at..].starts_with(framing) {
2743            return None;
2744        }
2745        real_at += framing.len();
2746        si_at = pos + subs[*i].0.len();
2747        let last = n + 1 == hits.len();
2748        let next_end = hits.get(n + 1).map(|(p, _)| *p).unwrap_or(stand_ins.len());
2749        let next_framing = &stand_ins[si_at..next_end];
2750        let rest = &real[real_at..];
2751        let follows = |after: &str| -> bool {
2752            if last {
2753                after == next_framing
2754            } else {
2755                after.starts_with(next_framing)
2756            }
2757        };
2758        let known = place_known_form(rest, &subs[*i].1, follows);
2759        let content_end = match known {
2760            Some(end) => end,
2761            None if placement == Placement::KnownFormsOnly => return None,
2762            None if last => {
2763                if !rest.ends_with(next_framing) {
2764                    return None;
2765                }
2766                rest.len() - next_framing.len()
2767            }
2768            None => {
2769                if next_framing.is_empty() {
2770                    return None;
2771                }
2772                let mut it = rest.match_indices(next_framing);
2773                match (it.next(), it.next()) {
2774                    (Some((p, _)), None) => p,
2775                    _ => return None,
2776                }
2777            }
2778        };
2779        pieces.push((framing.to_string(), false));
2780        pieces.push((rest[..content_end].to_string(), true));
2781        real_at += content_end;
2782    }
2783    pieces.push((stand_ins[si_at..].to_string(), false));
2784    Some(pieces)
2785}
2786
2787#[cfg(test)]
2788mod glm_call_tests {
2789    use super::*;
2790
2791    fn args(raw: &str) -> (String, serde_json::Value) {
2792        args_with(raw, None)
2793    }
2794
2795    fn args_with(raw: &str, schemas: Option<&ToolSchemas>) -> (String, serde_json::Value) {
2796        let (n, a) = parse_glm_tool_call(raw, schemas).expect("parses");
2797        (n, serde_json::from_str(&a).unwrap())
2798    }
2799
2800    fn schemas(tools: serde_json::Value) -> ToolSchemas {
2801        ToolSchemas::from_values(tools.as_array().unwrap().iter())
2802    }
2803
2804    #[test]
2805    fn reads_glm_bodies_framed_or_not() {
2806        let (n, a) = args("get_weather<arg_key>city</arg_key><arg_value>Paris</arg_value>");
2807        assert_eq!(n, "get_weather");
2808        assert_eq!(a, serde_json::json!({"city": "Paris"}));
2809        let (n2, a2) = args("<tool_call>get_weather<arg_key>city</arg_key><arg_value>Paris</arg_value></tool_call>");
2810        assert_eq!((n2, a2), (n, a));
2811    }
2812
2813    #[test]
2814    fn non_strings_come_back_typed_strings_stay_raw() {
2815        let (_, a) = args(
2816            "f<arg_key>n</arg_key><arg_value>3</arg_value><arg_key>flag</arg_key><arg_value>true</arg_value>\
2817             <arg_key>o</arg_key><arg_value>{\"x\": [1, 2]}</arg_value><arg_key>s</arg_key><arg_value>\"quoted\"</arg_value>\
2818             <arg_key>txt</arg_key><arg_value>a < b & {not json</arg_value>",
2819        );
2820        assert_eq!(
2821            a,
2822            serde_json::json!({"n": 3, "flag": true, "o": {"x": [1, 2]}, "s": "\"quoted\"", "txt": "a < b & {not json"})
2823        );
2824    }
2825
2826    #[test]
2827    fn declared_strings_stay_strings_and_declared_types_are_honoured() {
2828        let s = schemas(serde_json::json!([{
2829            "type": "function",
2830            "function": {"name": "f", "parameters": {"type": "object", "properties": {
2831                "id": {"type": "string"}, "flag": {"type": "string"}, "nil": {"type": "string"},
2832                "obj": {"type": "string"},
2833                "n": {"type": "integer"}, "on": {"type": "boolean"}, "list": {"type": "array"},
2834                "maybe": {"type": ["integer", "null"]}, "either": {"anyOf": [{"type": "integer"}, {"type": "string"}]},
2835                "pick": {"enum": ["1", "2"]},
2836                "loose": {}
2837            }}}
2838        }]));
2839        let raw = "f<arg_key>id</arg_key><arg_value>3</arg_value><arg_key>flag</arg_key><arg_value>true</arg_value>\
2840                   <arg_key>nil</arg_key><arg_value>null</arg_value><arg_key>obj</arg_key><arg_value>{\"a\": 1}</arg_value>\
2841                   <arg_key>n</arg_key><arg_value>42</arg_value><arg_key>on</arg_key><arg_value>false</arg_value>\
2842                   <arg_key>list</arg_key><arg_value>[1, \"x\"]</arg_value><arg_key>maybe</arg_key><arg_value>null</arg_value>\
2843                   <arg_key>either</arg_key><arg_value>7</arg_value><arg_key>pick</arg_key><arg_value>2</arg_value>\
2844                   <arg_key>loose</arg_key><arg_value>5</arg_value><arg_key>undeclared</arg_key><arg_value>false</arg_value>";
2845        let (_, a) = args_with(raw, Some(&s));
2846        assert_eq!(
2847            a,
2848            serde_json::json!({
2849                "id": "3", "flag": "true", "nil": "null", "obj": "{\"a\": 1}",
2850                "n": 42, "on": false, "list": [1, "x"], "maybe": null, "either": 7, "pick": "2",
2851                "loose": 5, "undeclared": false
2852            })
2853        );
2854        let (_, b) = args_with("g<arg_key>id</arg_key><arg_value>3</arg_value>", Some(&s));
2855        assert_eq!(b, serde_json::json!({"id": 3}));
2856        let anth = schemas(serde_json::json!([{"name": "f", "input_schema": {"properties": {"id": {"type": "string"}}}}]));
2857        assert_eq!(args_with("f<arg_key>id</arg_key><arg_value>3</arg_value>", Some(&anth)).1, serde_json::json!({"id": "3"}));
2858    }
2859
2860    #[test]
2861    fn string_values_keep_their_boundary_whitespace() {
2862        let raw = "write<arg_key>content</arg_key><arg_value>\n    indented\n</arg_value>\
2863                   <arg_key>pad</arg_key><arg_value>  x  </arg_value><arg_key>n</arg_key><arg_value> 3 </arg_value>";
2864        let (_, a) = args(raw);
2865        assert_eq!(a, serde_json::json!({"content": "\n    indented\n", "pad": "  x  ", "n": 3}));
2866        let s = schemas(serde_json::json!([{"name": "write", "parameters": {"properties": {"n": {"type": "string"}}}}]));
2867        assert_eq!(args_with(raw, Some(&s)).1["n"], " 3 ");
2868        let (n, a) = args("<tool_call>f\n<arg_key>k</arg_key>\n<arg_value>v</arg_value>\n</tool_call>");
2869        assert_eq!((n.as_str(), a), ("f", serde_json::json!({"k": "v"})));
2870    }
2871
2872    #[test]
2873    fn a_call_cut_inside_an_argument_is_not_a_call() {
2874        for cut in [
2875            "f<arg_key>path</arg_key><arg_value>/etc/pas",
2876            "f<arg_key>path</arg_key><arg_value>",
2877            "f<arg_key>path</arg_key>",
2878            "f<arg_key>pa",
2879            "f<arg_key>",
2880            "f<arg_key>a</arg_key><arg_value>1</arg_value><arg_key>b</arg_key><arg_value>par",
2881            "f<arg_key>a</arg_key><arg_value>1</arg_value><arg_key>b",
2882        ] {
2883            assert_eq!(parse_glm_tool_call(cut, None), None, "{cut:?}");
2884        }
2885        assert!(parse_glm_tool_call("f<arg_key>a</arg_key><arg_value>1</arg_value>", None).is_some());
2886        assert_eq!(
2887            parse_glm_tool_call("f<arg_key>a</arg_key>junk<arg_value>1</arg_value>", None),
2888            None
2889        );
2890        for junk in [
2891            "f<arg_key>a</arg_key><arg_value>1</arg_value>junk",
2892            "f<arg_key>a</arg_key><arg_value>1</arg_value>junk<arg_key>b</arg_key><arg_value>2</arg_value>",
2893        ] {
2894            assert_eq!(parse_glm_tool_call(junk, None), None, "{junk:?}");
2895        }
2896        assert!(parse_glm_tool_call("f<arg_key>a</arg_key><arg_value>1</arg_value>\n<arg_key>b</arg_key><arg_value>2</arg_value>\n", None).is_some());
2897    }
2898
2899    #[test]
2900    fn refs_and_non_string_value_sets_are_honoured() {
2901        let tool = serde_json::json!({"name": "f", "parameters": {
2902            "type": "object",
2903            "$defs": {"Id": {"type": "string"}, "Level": {"enum": [1, 2, 3]}, "Alias": {"$ref": "#/$defs/Id"}},
2904            "properties": {
2905                "id": {"$ref": "#/$defs/Id"}, "alias": {"$ref": "#/$defs/Alias"},
2906                "level": {"$ref": "#/$defs/Level"}, "one": {"const": 1}, "mixed": {"enum": ["a", 2, null]},
2907                "either": {"anyOf": [{"$ref": "#/$defs/Id"}, {"type": "null"}]}
2908            }
2909        }});
2910        let s = schemas(serde_json::json!([tool.clone()]));
2911        let raw = "f<arg_key>id</arg_key><arg_value>3</arg_value><arg_key>alias</arg_key><arg_value>true</arg_value>\
2912                   <arg_key>level</arg_key><arg_value>2</arg_value><arg_key>one</arg_key><arg_value>1</arg_value>\
2913                   <arg_key>mixed</arg_key><arg_value>a</arg_value><arg_key>either</arg_key><arg_value>null</arg_value>";
2914        assert_eq!(
2915            args_with(raw, Some(&s)).1,
2916            serde_json::json!({"id": "3", "alias": "true", "level": 2, "one": 1, "mixed": "a", "either": null})
2917        );
2918
2919        let tag: serde_json::Value = serde_json::from_str(
2920            &ToolEnvelope::Glm.structural_tag(&[tool.to_string()], "<tool_call>", "</tool_call>", true).unwrap(),
2921        )
2922        .unwrap();
2923        let el = tag["format"]["tags"][0]["content"]["elements"].as_array().unwrap().clone();
2924        let value = |i: usize| el[i]["content"]["elements"][1]["content"].clone();
2925        assert_eq!(value(1)["type"], "any_text", "id: a string through its ref");
2926        assert_eq!(value(2)["type"], "any_text", "alias: a ref to a ref to a string");
2927        assert_eq!(
2928            value(3),
2929            serde_json::json!({"type": "or", "elements": [
2930                {"type": "const_string", "value": "1"}, {"type": "const_string", "value": "2"}, {"type": "const_string", "value": "3"}
2931            ]})
2932        );
2933        assert_eq!(value(4), serde_json::json!({"type": "const_string", "value": "1"}));
2934        assert_eq!(
2935            value(5),
2936            serde_json::json!({"type": "or", "elements": [
2937                {"type": "const_string", "value": "a"}, {"type": "const_string", "value": "2"}, {"type": "const_string", "value": "null"}
2938            ]})
2939        );
2940        assert_eq!(python_json(&serde_json::json!({"k": [1, "é"], "n": null})), "{\"k\": [1, \"é\"], \"n\": null}");
2941    }
2942
2943    #[test]
2944    fn a_root_level_ref_names_the_argument_object() {
2945        let tool = serde_json::json!({"name": "f", "parameters": {
2946            "$ref": "#/$defs/Args",
2947            "$defs": {
2948                "Args": {"type": "object", "properties": {"id": {"$ref": "#/$defs/Id"}, "n": {"type": "integer"}}, "required": ["id"]},
2949                "Id": {"type": "string"}
2950            }
2951        }});
2952        let s = schemas(serde_json::json!([tool.clone()]));
2953        assert_eq!(parse_glm_tool_call("f", Some(&s)), None, "`id` is required");
2954        assert_eq!(
2955            args_with("f<arg_key>id</arg_key><arg_value>3</arg_value><arg_key>n</arg_key><arg_value>4</arg_value>", Some(&s)).1,
2956            serde_json::json!({"id": "3", "n": 4})
2957        );
2958        let tag: serde_json::Value = serde_json::from_str(
2959            &ToolEnvelope::Glm.structural_tag(&[tool.to_string()], "<tool_call>", "</tool_call>", true).unwrap(),
2960        )
2961        .unwrap();
2962        let el = tag["format"]["tags"][0]["content"]["elements"].as_array().unwrap().clone();
2963        assert_eq!(el[1]["elements"][0]["value"], "<arg_key>id</arg_key>", "required, so not optional");
2964        assert_eq!(el[1]["elements"][1]["content"]["type"], "any_text");
2965        assert_eq!(el[2]["type"], "optional");
2966        assert_eq!(el[2]["content"]["elements"][1]["content"]["json_schema"]["type"], "integer");
2967    }
2968
2969    #[test]
2970    fn ref_siblings_composed_sets_and_padded_nullable_strings() {
2971        let tool = serde_json::json!({"name": "f", "parameters": {
2972            "$ref": "#/$defs/Args",
2973            "required": ["extra"],
2974            "properties": {"extra": {"type": "string"}},
2975            "$defs": {
2976                "Args": {"type": "object", "properties": {"id": {"type": "string"}}, "required": ["id"]},
2977                "Mode": {"type": "string"}
2978            }
2979        }});
2980        let s = schemas(serde_json::json!([tool.clone()]));
2981        assert_eq!(
2982            parse_glm_tool_call("f<arg_key>id</arg_key><arg_value>1</arg_value>", Some(&s)),
2983            None,
2984            "`extra`, required beside the $ref, is missing"
2985        );
2986        assert!(parse_glm_tool_call(
2987            "f<arg_key>id</arg_key><arg_value>1</arg_value><arg_key>extra</arg_key><arg_value>x</arg_value>",
2988            Some(&s)
2989        )
2990        .is_some());
2991        let tag: serde_json::Value = serde_json::from_str(
2992            &ToolEnvelope::Glm.structural_tag(&[tool.to_string()], "<tool_call>", "</tool_call>", true).unwrap(),
2993        )
2994        .unwrap();
2995        let keys: Vec<String> = tag["format"]["tags"][0]["content"]["elements"]
2996            .as_array()
2997            .unwrap()
2998            .iter()
2999            .filter_map(|e| e["elements"][0]["value"].as_str().map(String::from))
3000            .collect();
3001        assert_eq!(keys, ["<arg_key>id</arg_key>", "<arg_key>extra</arg_key>"], "both required");
3002
3003        let tool = serde_json::json!({"name": "g", "parameters": {"type": "object",
3004            "$defs": {"Mode": {"type": "string"}},
3005            "properties": {
3006                "mode": {"$ref": "#/$defs/Mode", "enum": ["r", "w"]},
3007                "pick": {"anyOf": [{"const": "a"}, {"oneOf": [{"const": "b"}, {"enum": [1]}]}]},
3008                "open": {"anyOf": [{"const": "a"}, {"type": "string"}]},
3009                "maybe": {"type": ["string", "null"]}
3010            },
3011            "required": ["mode", "pick", "open", "maybe"]
3012        }});
3013        let tag: serde_json::Value = serde_json::from_str(
3014            &ToolEnvelope::Glm.structural_tag(&[tool.to_string()], "<tool_call>", "</tool_call>", true).unwrap(),
3015        )
3016        .unwrap();
3017        let el = tag["format"]["tags"][0]["content"]["elements"].as_array().unwrap().clone();
3018        let value = |i: usize| el[i]["elements"][1]["content"].clone();
3019        let consts = |vs: &[&str]| {
3020            serde_json::json!({"type": "or", "elements": vs.iter().map(|v| serde_json::json!({"type": "const_string", "value": v})).collect::<Vec<_>>()})
3021        };
3022        assert_eq!(value(1), consts(&["r", "w"]));
3023        assert_eq!(value(2), consts(&["a", "b", "1"]));
3024        assert_eq!(value(3)["type"], "any_text", "an open alternative keeps it open");
3025
3026        let s = schemas(serde_json::json!([tool]));
3027        let read = |v: &str| {
3028            args_with(
3029                &format!("g<arg_key>mode</arg_key><arg_value>r</arg_value><arg_key>pick</arg_key><arg_value>a</arg_value>\
3030                          <arg_key>open</arg_key><arg_value>a</arg_value><arg_key>maybe</arg_key><arg_value>{v}</arg_value>"),
3031                Some(&s),
3032            )
3033            .1["maybe"]
3034                .clone()
3035        };
3036        assert_eq!(read("null"), serde_json::Value::Null, "tojson's null");
3037        assert_eq!(read(" null "), " null ", "padded: written raw, so the string");
3038        assert_eq!(read("\nnull"), "\nnull");
3039    }
3040
3041    #[test]
3042    fn real_glm_5_3_calls_read_exactly() {
3043        let s = schemas(serde_json::json!([
3044            {"type": "function", "function": {"name": "write", "parameters": {"type": "object",
3045                "properties": {"filePath": {"type": "string"}, "content": {"type": "string"}}, "required": ["filePath", "content"]}}},
3046            {"type": "function", "function": {"name": "bash", "parameters": {"type": "object",
3047                "properties": {"command": {"type": "string"}, "timeout": {"type": "integer"}, "description": {"type": "string"}},
3048                "required": ["command", "description"]}}},
3049            {"type": "function", "function": {"name": "todowrite", "parameters": {"type": "object",
3050                "properties": {"todos": {"type": "array", "items": {"type": "object"}}}, "required": ["todos"]}}},
3051            {"type": "function", "function": {"name": "set_port", "parameters": {"type": "object",
3052                "properties": {"port": {"type": "string"}, "enabled": {"type": "boolean"}}, "required": ["port", "enabled"]}}}
3053        ]));
3054        let cases = [
3055            (
3056                "set_port<arg_key>port</arg_key><arg_value>8080</arg_value><arg_key>enabled</arg_key><arg_value>true</arg_value>",
3057                serde_json::json!({"port": "8080", "enabled": true}),
3058            ),
3059            (
3060                "write<arg_key>filePath</arg_key><arg_value>/tmp/demo/app.py</arg_value><arg_key>content</arg_key><arg_value>from flask import Flask, jsonify\n\napp = Flask(__name__)\n\n\n@app.route(\"/\")\ndef index():\n    return jsonify(message=\"Hello, World!\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=5000)\n</arg_value>",
3061                serde_json::json!({"filePath": "/tmp/demo/app.py", "content": "from flask import Flask, jsonify\n\napp = Flask(__name__)\n\n\n@app.route(\"/\")\ndef index():\n    return jsonify(message=\"Hello, World!\")\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"0.0.0.0\", port=5000)\n"}),
3062            ),
3063            (
3064                "todowrite<arg_key>todos</arg_key><arg_value>[{\"content\": \"Read and analyze the RFI PDF (requirements, deadlines, submission format)\", \"status\": \"in_progress\", \"priority\": \"high\"}, {\"content\": \"Extract key questions and map them to our capabilities/evidence\", \"status\": \"pending\", \"priority\": \"high\"}]</arg_value>",
3065                serde_json::json!({"todos": [
3066                    {"content": "Read and analyze the RFI PDF (requirements, deadlines, submission format)", "status": "in_progress", "priority": "high"},
3067                    {"content": "Extract key questions and map them to our capabilities/evidence", "status": "pending", "priority": "high"}
3068                ]}),
3069            ),
3070            (
3071                "bash<arg_key>command</arg_key><arg_value>ls -lh ./inputs</arg_value><arg_key>description</arg_key><arg_value>List files in ./inputs with sizes</arg_value>",
3072                serde_json::json!({"command": "ls -lh ./inputs", "description": "List files in ./inputs with sizes"}),
3073            ),
3074        ];
3075        for (body, want) in cases {
3076            assert_eq!(args_with(body, Some(&s)).1, want, "{body}");
3077        }
3078    }
3079
3080    #[test]
3081    fn required_arguments_must_be_present_when_the_schema_is_known() {
3082        let s = schemas(serde_json::json!([
3083            {"name": "write_file", "parameters": {"type": "object",
3084                "properties": {"path": {"type": "string"}, "content": {"type": "string"}, "mode": {"type": "string"}},
3085                "required": ["path", "content"]}},
3086            {"name": "list_files", "parameters": {"type": "object", "properties": {"dir": {"type": "string"}}}}
3087        ]));
3088        for cut in [
3089            "<tool_call>write_file",
3090            "write_file<arg_key>path</arg_key><arg_value>/a</arg_value>",
3091            "write_file<arg_key>content</arg_key><arg_value>x</arg_value><arg_key>mode</arg_key><arg_value>w</arg_value>",
3092        ] {
3093            assert_eq!(parse_glm_tool_call(cut, Some(&s)), None, "{cut:?}");
3094        }
3095        assert!(parse_glm_tool_call(
3096            "write_file<arg_key>path</arg_key><arg_value>/a</arg_value><arg_key>content</arg_key><arg_value></arg_value>",
3097            Some(&s)
3098        )
3099        .is_some());
3100        assert_eq!(parse_glm_tool_call("list_files", Some(&s)), Some(("list_files".into(), "{}".into())));
3101        assert!(parse_glm_tool_call("write_file", None).is_some());
3102    }
3103
3104    #[test]
3105    fn no_arguments_and_bad_names() {
3106        assert_eq!(parse_glm_tool_call("<tool_call>ping</tool_call>", None), Some(("ping".into(), "{}".into())));
3107        assert_eq!(parse_glm_tool_call("<tool_call>\nping\n</tool_call>", None), Some(("ping".into(), "{}".into())));
3108        assert_eq!(parse_glm_tool_call("{\"name\": \"f\"}", None), None, "JSON is not a GLM body");
3109        assert_eq!(parse_glm_tool_call("  ", None), None);
3110        assert_eq!(parse_glm_tool_call("<tool_call>I cannot call that</tool_call>", None), None);
3111        assert_eq!(
3112            parse_glm_tool_call("I will call f<arg_key>a</arg_key><arg_value>1</arg_value>", None),
3113            None
3114        );
3115        assert_eq!(
3116            parse_glm_tool_call("mcp__fs.read-file<arg_key>a</arg_key><arg_value>1</arg_value>", None).map(|c| c.0),
3117            Some("mcp__fs.read-file".into())
3118        );
3119    }
3120
3121    #[test]
3122    fn forced_choice_gets_a_structural_tag_and_auto_stays_free() {
3123        let tool = serde_json::json!({"type": "function", "function": {"name": "write_file", "parameters": {
3124            "type": "object",
3125            "properties": {"path": {"type": "string"}, "mode": {"enum": ["w", "a"]}, "lines": {"type": "integer"}},
3126            "required": ["path"]
3127        }}})
3128        .to_string();
3129        let e = ToolEnvelope::Glm;
3130        assert_eq!(e.structural_tag(std::slice::from_ref(&tool), "<tool_call>", "</tool_call>", false), None);
3131        let tag: serde_json::Value = serde_json::from_str(
3132            &e.structural_tag(std::slice::from_ref(&tool), "<tool_call>", "</tool_call>", true).expect("forced tag"),
3133        )
3134        .unwrap();
3135        assert_eq!(tag["type"], "structural_tag");
3136        let f = &tag["format"];
3137        assert_eq!((f["type"].as_str(), f["at_least_one"].as_bool()), (Some("triggered_tags"), Some(true)));
3138        assert_eq!(f["stop_after_first"], true);
3139        assert_eq!(f["triggers"], serde_json::json!(["<tool_call>"]));
3140        let t = &f["tags"][0];
3141        assert_eq!((t["begin"].as_str(), t["end"].as_str()), (Some("<tool_call>"), Some("</tool_call>")));
3142        let el = t["content"]["elements"].as_array().unwrap();
3143        assert_eq!(el[0], serde_json::json!({"type": "const_string", "value": "write_file"}));
3144        assert_eq!(el[1]["elements"][0]["value"], "<arg_key>path</arg_key>");
3145        assert_eq!(el[1]["elements"][1]["begin"], "<arg_value>");
3146        assert_eq!(el[1]["elements"][1]["content"]["type"], "any_text");
3147        assert_eq!(el[1]["elements"][1]["end"], "</arg_value>");
3148        assert_eq!(el[2]["type"], "optional");
3149        assert_eq!(el[2]["content"]["elements"][1]["content"]["type"], "or");
3150        assert_eq!(el[3]["content"]["elements"][1]["content"]["type"], "json_schema");
3151        assert_eq!(el[3]["content"]["elements"][1]["content"]["json_schema"], serde_json::json!({"type": "integer"}));
3152        assert_eq!(e.call_grammar(&[tool]), None);
3153
3154        let tool = serde_json::json!({"name": "op", "parameters": {
3155            "type": "object",
3156            "$defs": {"P": {"type": "object", "properties": {"x": {"type": "integer"}}}},
3157            "properties": {"kind": {"type": "string", "const": "move"}, "to": {"type": "array", "items": {"$ref": "#/$defs/P"}}},
3158            "required": ["kind", "to", "why"]
3159        }})
3160        .to_string();
3161        let tag: serde_json::Value =
3162            serde_json::from_str(&e.structural_tag(&[tool], "<tool_call>", "</tool_call>", true).unwrap()).unwrap();
3163        let el = tag["format"]["tags"][0]["content"]["elements"].as_array().unwrap().clone();
3164        assert_eq!(el[1]["elements"][1]["content"], serde_json::json!({"type": "const_string", "value": "move"}));
3165        assert_eq!(el[2]["elements"][1]["content"]["json_schema"]["$defs"]["P"]["type"], "object");
3166        assert_eq!(el[3]["elements"][0]["value"], "<arg_key>why</arg_key>");
3167        assert_eq!(el[3]["elements"][1]["content"]["type"], "any_text");
3168    }
3169
3170    #[test]
3171    fn glm_override_is_taken_where_the_template_spells_the_glm_wire() {
3172        struct T(Option<&'static str>);
3173        impl TextCodec for T {
3174            fn encode(&self, text: &str) -> Vec<u32> {
3175                MockCodec.encode(text)
3176            }
3177            fn token_bytes(&self, token: u32) -> Vec<u8> {
3178                MockCodec.token_bytes(token)
3179            }
3180            fn chat_template_source(&self) -> Option<String> {
3181                self.0.map(str::to_string)
3182            }
3183        }
3184        let glm = T(Some(include_str!("../../../tests/fixtures/chat_templates/glm_5_3.jinja")));
3185        assert_eq!(glm.tool_envelope(), ToolEnvelope::Json, "a wire the learner missed reads as JSON");
3186        assert_eq!(tool_envelope_override_conflict(&glm, ToolEnvelope::Glm), None);
3187        assert_eq!(tool_envelope_override_conflict(&glm, ToolEnvelope::Json), None, "a no-op");
3188        assert!(tool_envelope_override_conflict(&glm, ToolEnvelope::Gemma).is_some());
3189        let chatml = T(Some("{% for m in messages %}<|im_start|>{{ m.content }}{% endfor %}<tool_call>"));
3190        assert!(tool_envelope_override_conflict(&chatml, ToolEnvelope::Glm).is_some());
3191        assert!(tool_envelope_override_conflict(&T(None), ToolEnvelope::Glm).is_some());
3192    }
3193}
3194
3195#[cfg(all(test, feature = "basert"))]
3196mod stand_in_tests {
3197    use super::*;
3198    use Placement::*;
3199
3200    fn subs(values: &[&str]) -> Vec<(String, String)> {
3201        values.iter().enumerate().map(|(i, v)| (stand_in_key(i), v.to_string())).collect()
3202    }
3203
3204    fn rebuilt(pieces: &[(String, bool)]) -> String {
3205        pieces.iter().map(|(t, _)| t.as_str()).collect()
3206    }
3207
3208    #[test]
3209    fn repeated_framing_between_array_items_is_placed_by_the_known_forms() {
3210        let s = subs(&["a", "b", "c <|im_end|>"]);
3211        let stand_ins = format!(r#"{{"paths": ["{}", "{}", "{}"]}}"#, s[0].0, s[1].0, s[2].0);
3212        let real = r#"{"paths": ["a", "b", "c <|im_end|>"]}"#;
3213        let pieces = split_around_stand_ins(&stand_ins, &s, real, KnownFormsOnly).expect("placed");
3214        assert_eq!(rebuilt(&pieces), real);
3215        let content: Vec<&str> = pieces.iter().filter(|(_, c)| *c).map(|(t, _)| t.as_str()).collect();
3216        assert_eq!(content, ["a", "b", r"c <|im_end|>"]);
3217        let framing: Vec<&str> = pieces.iter().filter(|(_, c)| !*c).map(|(t, _)| t.as_str()).collect();
3218        assert_eq!(framing, [r#"{"paths": [""#, r#"", ""#, r#"", ""#, r#""]}"#]);
3219    }
3220
3221    #[test]
3222    fn a_trimmed_message_is_placed_where_the_framing_follows_it() {
3223        let s = subs(&["abc\n"]);
3224        let stand_ins = format!("<start_of_turn>user\n{}\n<end_of_turn>", s[0].0);
3225        let real = "<start_of_turn>user\nabc\n<end_of_turn>";
3226        let pieces = split_around_stand_ins(&stand_ins, &s, real, KnownFormsOnly).expect("placed");
3227        assert_eq!(rebuilt(&pieces), real);
3228        assert_eq!(pieces[1], ("abc".to_string(), true));
3229        assert_eq!(pieces[2], ("\n<end_of_turn>".to_string(), false));
3230    }
3231
3232    #[test]
3233    fn unknown_forms_fall_back_to_a_unique_search_or_none() {
3234        let s = subs(&["x", "y"]);
3235        let stand_ins = format!("<a>{}</a><b>{}</b>", s[0].0, s[1].0);
3236        let pieces = split_around_stand_ins(&stand_ins, &s, "<a>X!</a><b>Y!</b>", SearchFraming).expect("placed");
3237        assert_eq!(pieces[1], ("X!".to_string(), true));
3238        assert_eq!(pieces[3], ("Y!".to_string(), true));
3239        assert!(split_around_stand_ins(&stand_ins, &s, "<a>X!</a><b>Y!</b>", KnownFormsOnly).is_none());
3240        assert!(split_around_stand_ins(&stand_ins, &s, "<a>X</a><b></a><b>Y</b>", SearchFraming).is_none());
3241        assert!(split_around_stand_ins("<a></a>", &s, "<a>x</a>", SearchFraming).is_none());
3242    }
3243
3244    #[test]
3245    fn framing_a_template_emits_only_for_the_real_text_is_never_content() {
3246        let s = subs(&["<tool_response>r</tool_response>"]);
3247        let stand_ins = format!("<|im_start|>user\n{}<|im_end|>", s[0].0);
3248        let real = "<|im_start|>user\n<wrapped><tool_response>r</tool_response></wrapped><|im_end|>";
3249        assert!(split_around_stand_ins(&stand_ins, &s, real, KnownFormsOnly).is_none());
3250        let pieces = split_around_stand_ins(&stand_ins, &s, real, SearchFraming).expect("placed");
3251        assert_eq!(pieces[1].0, "<wrapped><tool_response>r</tool_response></wrapped>");
3252    }
3253
3254    #[test]
3255    fn known_forms_cover_tojson_and_trim() {
3256        let always = |_: &str| true;
3257        let original = " <x> & 'y'\n";
3258        assert_eq!(place_known_form(" <x> & 'y'\n|", original, always), Some(original.len()));
3259        assert_eq!(place_known_form("<x> & 'y'|", original, always), Some("<x> & 'y'".len()));
3260        let html_safe = r" <x> & 'y'\n";
3261        assert_eq!(place_known_form(&format!("{html_safe}|"), original, always), Some(html_safe.len()));
3262        assert_eq!(place_known_form(" <x> & 'y'\\n|", original, always), Some(" <x> & 'y'\\n".len()));
3263        assert_eq!(place_known_form("other", "plain", always), None);
3264        assert_eq!(place_known_form("abc\n<e>", "abc\n", |after: &str| after == "\n<e>"), Some(3));
3265    }
3266}
3267
3268#[derive(Debug, Clone, Copy)]
3269enum Piece<'a> {
3270    Framing(&'a str),
3271    Content(&'a str),
3272}
3273
3274impl<'a> Piece<'a> {
3275    fn as_pair(&self) -> (&'a str, bool) {
3276        match *self {
3277            Piece::Framing(t) => (t, false),
3278            Piece::Content(t) => (t, true),
3279        }
3280    }
3281}
3282
3283fn pieces_of(owned: &[(String, bool)]) -> Vec<Piece<'_>> {
3284    owned
3285        .iter()
3286        .map(|(s, content)| {
3287            if *content {
3288                Piece::Content(s)
3289            } else {
3290                Piece::Framing(s)
3291            }
3292        })
3293        .collect()
3294}
3295
3296pub struct ChatMlCodec {
3297    inner: BundleCodec,
3298    im_start: u32,
3299    im_end: u32,
3300    newline: u32,
3301    think_open: Option<u32>,
3302    think_close: Option<u32>,
3303    tool_open: u32,
3304    tool_close: u32,
3305    think: ThinkSuffixes,
3306    template: Option<Box<crate::template_codec::TemplateCodec>>,
3307    derive_tool_wire: bool,
3308    thinking_switch: bool,
3309    /// What the template puts between an assistant turn's role line and its
3310    /// content: in history (a user query follows it), and where none does.
3311    assistant_openers: std::sync::OnceLock<AssistantOpeners>,
3312}
3313
3314/// See `ChatMlCodec::assistant_openers`. Qwen3.5 opens only a turn no
3315/// query follows with its (empty) reasoning block; Qwen3.8 keeps the block
3316/// on every turn unless told not to preserve it.
3317#[derive(Debug, Clone, Default, PartialEq, Eq)]
3318struct AssistantOpeners {
3319    in_history: String,
3320    last: String,
3321}
3322
3323#[derive(Debug, Clone, PartialEq, Eq)]
3324struct ThinkSuffixes {
3325    default: Vec<u32>,
3326    on: Vec<u32>,
3327    off: Vec<u32>,
3328}
3329
3330impl ChatMlCodec {
3331    #[cfg(feature = "basert")]
3332    pub fn load(model_path: &std::path::Path) -> Result<ChatMlCodec, Status> {
3333        let tok = superfluid_engine_ffi::TokenizerHandle::load(model_path).map_err(|_| Status::Fatal)?;
3334        Self::from_tokenizer(std::sync::Arc::new(tok))
3335    }
3336
3337    pub fn from_tokenizer(
3338        tok: std::sync::Arc<dyn superfluid_engine::Tokenizer>,
3339    ) -> Result<ChatMlCodec, Status> {
3340        let inner = BundleCodec::from_tokenizer(std::sync::Arc::clone(&tok));
3341        let one = |s: &str| -> Result<u32, Status> {
3342            let t = inner.encode(s);
3343            if t.len() == 1 {
3344                Ok(t[0])
3345            } else {
3346                Err(Status::Unsupported)
3347            }
3348        };
3349        let tmpl = crate::template_codec::TemplateCodec::from_tokenizer(tok).ok();
3350        let (think_open, think_close) = match (one("<think>"), one("</think>")) {
3351            (Ok(o), Ok(c)) => (Some(o), Some(c)),
3352            _ => (None, None),
3353        };
3354        let mut codec = ChatMlCodec {
3355            im_start: one("<|im_start|>")?,
3356            im_end: one("<|im_end|>")?,
3357            newline: one("\n")?,
3358            think_open,
3359            think_close,
3360            tool_open: one("<tool_call>")?,
3361            tool_close: one("</tool_call>")?,
3362            inner,
3363            think: ThinkSuffixes { default: Vec::new(), on: Vec::new(), off: Vec::new() },
3364            derive_tool_wire: tmpl.as_ref().is_some_and(|t| !t.tool_body_is_json()),
3365            template: tmpl.map(Box::new),
3366            thinking_switch: false,
3367            assistant_openers: std::sync::OnceLock::new(),
3368        };
3369        codec.think = codec.derive_think_suffixes().unwrap_or_else(|| codec.assumed_think_suffixes());
3370        codec.thinking_switch = thinking_switch_changes(|msgs, tools, on| {
3371            let mut kw = serde_json::Map::new();
3372            kw.insert("enable_thinking".into(), serde_json::Value::Bool(on));
3373            codec.render_prompt_structured_with(msgs, tools, &kw)
3374        });
3375        Ok(codec)
3376    }
3377
3378    pub fn with_thinking(mut self, enabled: bool) -> ChatMlCodec {
3379        self.think.default = if enabled { self.think.on.clone() } else { self.think.off.clone() };
3380        self
3381    }
3382
3383    fn derive_think_suffixes(&self) -> Option<ThinkSuffixes> {
3384        let t = self.template.as_deref()?;
3385        let (open, close) = (self.think_open?, self.think_close?);
3386        let mut opener = vec![self.im_start];
3387        opener.extend(self.inner.encode("assistant\n"));
3388        let probe = [ChatMessage::new(crate::wal::role::USER, "probe".to_string())];
3389        let suffix = |thinking: Option<bool>| -> Option<Vec<u32>> {
3390            let mut kw = serde_json::Map::new();
3391            if let Some(on) = thinking {
3392                kw.insert("enable_thinking".to_string(), serde_json::Value::Bool(on));
3393            }
3394            let with = t.render_prompt_structured_with(&probe, &[], &kw)?;
3395            let without = t.render_turns_structured_with(&probe, &[], &kw)?;
3396            let s = with.strip_prefix(without.as_slice())?.strip_prefix(opener.as_slice())?;
3397            let markup_only = s.iter().all(|&tok| {
3398                tok == open
3399                    || tok == close
3400                    || self.inner.token_bytes(tok).iter().all(u8::is_ascii_whitespace)
3401            });
3402            markup_only.then(|| s.to_vec())
3403        };
3404        Some(ThinkSuffixes { default: suffix(None)?, on: suffix(Some(true))?, off: suffix(Some(false))? })
3405    }
3406
3407    fn reopen_for(&self, mut prompt: Vec<u32>, kwargs: &serde_json::Map<String, serde_json::Value>) -> Vec<u32> {
3408        let Some(thinking) = kwargs.get("enable_thinking").and_then(|v| v.as_bool()) else {
3409            return prompt;
3410        };
3411        let unset = self.generation_prefix_with(TurnState::AfterInput, None);
3412        let want = self.generation_prefix_with(TurnState::AfterInput, Some(thinking));
3413        if let (Some(unset), Some(want)) = (unset, want) {
3414            if prompt.ends_with(&unset) {
3415                prompt.truncate(prompt.len() - unset.len());
3416                prompt.extend(want);
3417            }
3418        }
3419        prompt
3420    }
3421
3422    fn assumed_think_suffixes(&self) -> ThinkSuffixes {
3423        let off = if self.think_open.is_some() {
3424            self.inner.encode("<think>\n\n</think>\n\n")
3425        } else {
3426            Vec::new()
3427        };
3428        ThinkSuffixes { default: Vec::new(), on: Vec::new(), off }
3429    }
3430
3431    fn role_str(role: u32) -> &'static str {
3432        match role {
3433            crate::wal::role::SYSTEM => "system",
3434            crate::wal::role::ASSISTANT => "assistant",
3435            _ => "user",
3436        }
3437    }
3438
3439    fn inserted_text(
3440        t: &crate::template_codec::TemplateCodec,
3441        with: &[u32],
3442        without: &[u32],
3443    ) -> Option<String> {
3444        let pre = with
3445            .iter()
3446            .zip(without.iter())
3447            .take_while(|(a, b)| a == b)
3448            .count();
3449        let suf = with[pre..]
3450            .iter()
3451            .rev()
3452            .zip(without[pre..].iter().rev())
3453            .take_while(|(a, b)| a == b)
3454            .count();
3455        if with.len() <= pre + suf {
3456            return None;
3457        }
3458        let text = t.decode(&with[pre..with.len() - suf]);
3459        let trimmed = text.trim();
3460        (!trimmed.is_empty()).then(|| trimmed.to_string())
3461    }
3462
3463    fn tool_source(&self) -> Option<&crate::template_codec::TemplateCodec> {
3464        if self.derive_tool_wire {
3465            self.template.as_deref()
3466        } else {
3467            None
3468        }
3469    }
3470
3471    fn turn(&self, role: u32, pieces: &[Piece<'_>]) -> Vec<u32> {
3472        let role_line = format!("{}\n", Self::role_str(role));
3473        let mut body: Vec<(&str, bool)> = Vec::with_capacity(pieces.len() + 1);
3474        body.push((&role_line, false));
3475        body.extend(pieces.iter().map(Piece::as_pair));
3476        let mut span = vec![self.im_start];
3477        span.extend(self.inner.encode_pieces(&body));
3478        span.push(self.im_end);
3479        span.push(self.newline);
3480        span
3481    }
3482
3483    /// The template's text between an assistant turn's role line and its
3484    /// content, for a turn a later user query puts in history and for one
3485    /// that ends the conversation. Empty where there is no template, or the
3486    /// template carries the content somewhere else.
3487    fn derived_assistant_openers(&self) -> AssistantOpeners {
3488        let Some(t) = self.template.as_deref() else { return AssistantOpeners::default() };
3489        let before_content = |msgs: &[ChatMessage]| -> Option<String> {
3490            let text = self.inner.decode(&t.render_turns_structured(msgs, &[])?);
3491            let at = text.find(CONTENT_STAND_IN)?;
3492            let head = &text[..at];
3493            let role = format!("{}\n", Self::role_str(crate::wal::role::ASSISTANT));
3494            Some(head[head.rfind(&role)? + role.len()..].to_string())
3495        };
3496        let user = ChatMessage::new(crate::wal::role::USER, "x");
3497        let turn = ChatMessage::new(crate::wal::role::ASSISTANT, CONTENT_STAND_IN);
3498        AssistantOpeners {
3499            in_history: before_content(&[user.clone(), turn.clone(), ChatMessage::new(crate::wal::role::USER, "w")])
3500                .unwrap_or_default(),
3501            last: before_content(&[user, turn]).unwrap_or_default(),
3502        }
3503    }
3504
3505    fn assistant_openers(&self) -> &AssistantOpeners {
3506        self.assistant_openers.get_or_init(|| self.derived_assistant_openers())
3507    }
3508
3509    /// An assistant turn with `opener` after its role line; the turn as it is
3510    /// where the opener is empty or the turn does not begin as one.
3511    fn opened(&self, turn: Vec<u32>, opener: &str) -> Vec<u32> {
3512        if opener.is_empty() {
3513            return turn;
3514        }
3515        let role_line = self.inner.encode_pieces(&[(&format!("{}\n", Self::role_str(crate::wal::role::ASSISTANT)), false)]);
3516        let head = 1 + role_line.len();
3517        if turn.len() < head || turn[0] != self.im_start || turn[1..head] != role_line[..] {
3518            return turn;
3519        }
3520        let mut span = turn[..head].to_vec();
3521        span.extend(self.inner.encode_pieces(&[(opener, false)]));
3522        span.extend_from_slice(&turn[head..]);
3523        span
3524    }
3525
3526    fn frames_like(&self, t: &crate::template_codec::TemplateCodec) -> bool {
3527        let probe = [
3528            ChatMessage::new(crate::wal::role::USER, "x"),
3529            ChatMessage::new(crate::wal::role::ASSISTANT, "y"),
3530        ];
3531        match t.render_turns_structured(&probe, &[]) {
3532            None => false,
3533            Some(toks) => toks.contains(&self.im_start) && toks.contains(&self.im_end),
3534        }
3535    }
3536
3537    fn derived_system_body(
3538        &self,
3539        t: &crate::template_codec::TemplateCodec,
3540        system: Option<&str>,
3541        tools: &[String],
3542        kwargs: &serde_json::Map<String, serde_json::Value>,
3543    ) -> Option<Vec<(String, bool)>> {
3544        let body_for = |sys: &str| -> Option<String> {
3545            let msgs = [
3546                ChatMessage::new(crate::wal::role::SYSTEM, sys),
3547                ChatMessage::new(crate::wal::role::USER, "x"),
3548            ];
3549            let toks = t.render_turns_structured_with(&msgs, tools, kwargs)?;
3550            let open = toks.iter().position(|&x| x == self.im_start)?;
3551            let end = toks[open..].iter().position(|&x| x == self.im_end)? + open;
3552            let body = &toks[open + 1..end];
3553            let text = self.inner.decode(body);
3554            let role = format!("{}\n", Self::role_str(crate::wal::role::SYSTEM));
3555            Some(text.strip_prefix(&role).unwrap_or(&text).to_string())
3556        };
3557        let non_empty = |pieces: Vec<(String, bool)>| -> Option<Vec<(String, bool)>> {
3558            let joined: String = pieces.iter().map(|(s, _)| s.as_str()).collect();
3559            (!joined.trim().is_empty()).then_some(pieces)
3560        };
3561        let Some(sys) = system.filter(|s| !s.is_empty()) else {
3562            return non_empty(vec![(body_for("")?, false)]);
3563        };
3564        let reference = body_for(sys)?;
3565        let body = body_for(CONTENT_STAND_IN)?;
3566        let placed = split_once_exact(&body, CONTENT_STAND_IN).and_then(|(before, after)| {
3567            [sys, sys.trim()]
3568                .into_iter()
3569                .find(|used| {
3570                    reference.len() == before.len() + used.len() + after.len()
3571                        && reference.starts_with(before)
3572                        && reference.ends_with(after)
3573                        && &reference[before.len()..before.len() + used.len()] == *used
3574                })
3575                .map(|used| {
3576                    vec![
3577                        (before.to_string(), false),
3578                        (used.to_string(), true),
3579                        (after.to_string(), false),
3580                    ]
3581                })
3582        });
3583        match placed {
3584            Some(pieces) => non_empty(pieces),
3585            None => {
3586                tracing::debug!(
3587                    "chat template does not carry the system text verbatim; \
3588                     rendering it as framing"
3589                );
3590                non_empty(vec![(reference, false)])
3591            }
3592        }
3593    }
3594
3595    fn derived_tool_instructions(
3596        t: &crate::template_codec::TemplateCodec,
3597        tool_jsons: &[String],
3598    ) -> Option<String> {
3599        let msgs = [
3600            ChatMessage::new(crate::wal::role::SYSTEM, ""),
3601            ChatMessage::new(crate::wal::role::USER, "x"),
3602        ];
3603        let with = t.render_turns_structured(&msgs, tool_jsons)?;
3604        let without = t.render_turns_structured(&msgs, &[])?;
3605        Self::inserted_text(t, &with, &without)
3606    }
3607
3608    fn render_without_calls(t: &crate::template_codec::TemplateCodec, content: &str) -> Option<Vec<u32>> {
3609        let user = ChatMessage::new(crate::wal::role::USER, "x");
3610        let plain = ChatMessage::new(crate::wal::role::ASSISTANT, content);
3611        t.render_turns_structured(&[user, plain], &[])
3612    }
3613
3614    fn derived_call_block_against(
3615        t: &crate::template_codec::TemplateCodec,
3616        content: &str,
3617        calls: &[(String, String)],
3618        without: &[u32],
3619    ) -> Option<String> {
3620        let user = ChatMessage::new(crate::wal::role::USER, "x");
3621        let mut calling = ChatMessage::new(crate::wal::role::ASSISTANT, content);
3622        for (name, args) in calls {
3623            calling.tool_calls.push(ToolCallMsg {
3624                id: String::new(),
3625                name: name.clone(),
3626                arguments: args.clone(),
3627            });
3628        }
3629        let with = t.render_turns_structured(&[user, calling], &[])?;
3630        Self::inserted_text(t, &with, without)
3631    }
3632
3633    fn derived_call_pieces(
3634        t: &crate::template_codec::TemplateCodec,
3635        content: &str,
3636        calls: &[(String, String)],
3637    ) -> Option<Vec<(String, bool)>> {
3638        let without = Self::render_without_calls(t, content)?;
3639        let block = Self::derived_call_block_against(t, content, calls, &without)?;
3640        let mut subs: Vec<(String, String)> = Vec::new();
3641        let stand_in_calls: Vec<(String, String)> = calls
3642            .iter()
3643            .map(|(name, args)| {
3644                let args = match serde_json::from_str::<serde_json::Value>(args) {
3645                    Ok(mut v) => {
3646                        stand_in_strings(&mut v, &mut subs);
3647                        v.to_string()
3648                    }
3649                    Err(_) => args.clone(),
3650                };
3651                (name.clone(), args)
3652            })
3653            .collect();
3654        if subs.is_empty() {
3655            return Some(vec![(block, false)]);
3656        }
3657        let with_stand_ins = Self::derived_call_block_against(t, content, &stand_in_calls, &without)?;
3658        Some(split_around_stand_ins(&with_stand_ins, &subs, &block, Placement::SearchFraming).unwrap_or_else(|| {
3659            tracing::debug!(
3660                "chat template does not carry the call arguments verbatim; \
3661                 the call block is rendered as framing"
3662            );
3663            vec![(block, false)]
3664        }))
3665    }
3666}
3667
3668impl TextCodec for ChatMlCodec {
3669    fn encode(&self, text: &str) -> Vec<u32> {
3670        self.inner.encode(text)
3671    }
3672    fn token_bytes(&self, token: u32) -> Vec<u8> {
3673        self.inner.token_bytes(token)
3674    }
3675    fn chat_template_source(&self) -> Option<String> {
3676        self.inner.chat_template_source()
3677    }
3678    fn behavior_fingerprint(&self) -> Option<superfluid_fingerprint::BehaviorFingerprint> {
3679        let base = if self.think.default.is_empty() {
3680            "chatml"
3681        } else if self.think.default == self.think.off {
3682            "chatml/nothink"
3683        } else {
3684            "chatml/think"
3685        };
3686        let id = match &self.template {
3687            Some(t) => format!("{base}/tmpl:{}", t.render_identity()),
3688            None => base.to_string(),
3689        };
3690        Some(self.inner.fingerprint(&id, &self.channel_markers()))
3691    }
3692    fn encode_content(&self, text: &str) -> Vec<u32> {
3693        self.inner.encode_content(text)
3694    }
3695    fn encode_pieces(&self, pieces: &[(&str, bool)]) -> Vec<u32> {
3696        self.inner.encode_pieces(pieces)
3697    }
3698    fn render_message(&self, role: u32, text: &str) -> Option<Vec<u32>> {
3699        let turn = self.turn(role, &[Piece::Content(text)]);
3700        if role == crate::wal::role::ASSISTANT {
3701            return Some(self.opened(turn, &self.assistant_openers().in_history));
3702        }
3703        Some(turn)
3704    }
3705    fn render_turn_with_tokens(
3706        &self,
3707        role: u32,
3708        pre_text: &str,
3709        inner: &[u32],
3710        post_text: &str,
3711    ) -> Option<Vec<u32>> {
3712        let role_line = format!("{}\n", Self::role_str(role));
3713        let mut span = vec![self.im_start];
3714        span.extend(self.inner.encode_pieces(&[(&role_line, false), (pre_text, true)]));
3715        span.extend_from_slice(inner);
3716        if !post_text.is_empty() {
3717            span.extend(self.inner.encode_content(post_text));
3718        }
3719        span.push(self.im_end);
3720        span.push(self.newline);
3721        Some(span)
3722    }
3723    fn render_fim(&self, prefix: &str, suffix: &str, mode: u8) -> Option<Vec<u32>> {
3724        self.inner.render_fim(prefix, suffix, mode)
3725    }
3726    fn empty_think_tokens(&self) -> Option<Vec<u32>> {
3727        self.think_open?;
3728        Some(self.inner.encode("<think>\n\n</think>\n\n"))
3729    }
3730    fn generation_prefix(&self, state: TurnState) -> Option<Vec<u32>> {
3731        self.generation_prefix_with(state, None)
3732    }
3733    fn generation_prefix_with(&self, state: TurnState, thinking: Option<bool>) -> Option<Vec<u32>> {
3734        let suffix = match thinking {
3735            None => &self.think.default,
3736            Some(true) => &self.think.on,
3737            Some(false) => &self.think.off,
3738        };
3739        let opener = || {
3740            let mut s = vec![self.im_start];
3741            s.extend(self.inner.encode("assistant\n"));
3742            s.extend_from_slice(suffix);
3743            s
3744        };
3745        match state {
3746            TurnState::AfterInput => Some(opener()),
3747            TurnState::MidTurn => None,
3748            TurnState::AfterFinishedTurn => {
3749                let mut s = vec![self.newline];
3750                s.extend(opener());
3751                Some(s)
3752            }
3753        }
3754    }
3755    fn channel_markers(&self) -> Vec<(u32, u32, u32)> {
3756        let mut markers = Vec::new();
3757        if let (Some(o), Some(c)) = (self.think_open, self.think_close) {
3758            markers.push((o, c, crate::wal::channel::REASONING));
3759        }
3760        markers.push((
3761            self.tool_open,
3762            self.tool_close,
3763            crate::wal::channel::TOOL_CALL,
3764        ));
3765        markers
3766    }
3767
3768    fn turn_separator(&self) -> Option<Vec<u32>> {
3769        Some(vec![self.newline])
3770    }
3771
3772    fn render_system_with_tools(
3773        &self,
3774        system: Option<&str>,
3775        tool_jsons: &[String],
3776    ) -> Option<Vec<u32>> {
3777        let derived = self
3778            .tool_source()
3779            .and_then(|t| Self::derived_tool_instructions(t, tool_jsons));
3780        let mut pieces: Vec<Piece<'_>> = Vec::new();
3781        if let Some(sys) = system {
3782            pieces.push(Piece::Content(sys));
3783            pieces.push(Piece::Framing("\n\n"));
3784        }
3785        if let Some(tools_text) = derived {
3786            pieces.push(Piece::Framing(&tools_text));
3787            return Some(self.turn(crate::wal::role::SYSTEM, &pieces));
3788        }
3789        let mut body = String::from(
3790            "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>",
3791        );
3792        for t in tool_jsons {
3793            body.push('\n');
3794            body.push_str(t);
3795        }
3796        body.push_str(
3797            "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call>",
3798        );
3799        pieces.push(Piece::Framing(&body));
3800        Some(self.turn(crate::wal::role::SYSTEM, &pieces))
3801    }
3802
3803    fn tool_envelope(&self) -> ToolEnvelope {
3804        match self.tool_source() {
3805            Some(t) => t.tool_envelope(),
3806            None => ToolEnvelope::Json,
3807        }
3808    }
3809
3810    fn tool_call_delimiters(&self) -> Option<(String, String)> {
3811        match self.tool_source() {
3812            Some(t) => t.tool_call_delimiters(),
3813            None => Some((self.marker_literal(self.tool_open), self.marker_literal(self.tool_close))),
3814        }
3815    }
3816    fn marker_literal(&self, token: u32) -> String {
3817        self.inner.marker_literal(token)
3818    }
3819
3820    fn structural_tag(&self, tool_jsons: &[String], at_least_one: bool) -> Option<String> {
3821        match self.tool_source() {
3822            Some(t) => t.structural_tag(tool_jsons, at_least_one),
3823            None => {
3824                let (begin, end) = self.tool_call_delimiters()?;
3825                self.tool_envelope()
3826                    .structural_tag(tool_jsons, &begin, &end, at_least_one)
3827            }
3828        }
3829    }
3830
3831    fn call_grammar(&self, tool_jsons: &[String]) -> Option<String> {
3832        match self.tool_source() {
3833            Some(t) => t.call_grammar(tool_jsons),
3834            None => self.tool_envelope().call_grammar(tool_jsons),
3835        }
3836    }
3837
3838    fn named_tool_call(&self, raw: &str) -> Option<(String, String)> {
3839        self.tool_source()?.named_tool_call(raw)
3840    }
3841
3842    fn parse_tool_call_with(&self, raw: &str, schemas: Option<&ToolSchemas>) -> Option<(String, String)> {
3843        match self.tool_source() {
3844            Some(t) => t.parse_tool_call_with(raw, schemas),
3845            None => parse_json_tool_call(raw),
3846        }
3847    }
3848
3849    fn validate_template_kwargs(
3850        &self,
3851        kwargs: &serde_json::Map<String, serde_json::Value>,
3852    ) -> Result<(), String> {
3853        match (&self.template, kwargs.is_empty()) {
3854            (Some(t), false) => t.validate_template_kwargs(kwargs),
3855            _ => Ok(()),
3856        }
3857    }
3858
3859    fn supports_reasoning_effort(&self) -> bool {
3860        self.template.as_deref().is_some_and(|t| t.supports_reasoning_effort())
3861    }
3862
3863    fn reasoning_effort_levels(&self) -> Vec<&'static str> {
3864        self.template.as_deref().map(|t| t.reasoning_effort_levels()).unwrap_or_default()
3865    }
3866
3867    fn supports_enable_thinking(&self) -> bool {
3868        self.thinking_switch
3869    }
3870
3871    fn render_system_full(
3872        &self,
3873        system: Option<&str>,
3874        tool_jsons: &[String],
3875        kwargs: &serde_json::Map<String, serde_json::Value>,
3876    ) -> Option<Vec<u32>> {
3877        let derived = self
3878            .template
3879            .as_deref()
3880            .and_then(|t| self.derived_system_body(t, system, tool_jsons, kwargs));
3881        match (derived, tool_jsons.is_empty()) {
3882            (Some(body), _) => Some(self.turn(crate::wal::role::SYSTEM, &pieces_of(&body))),
3883            (None, true) => match system {
3884                Some(text) if !text.is_empty() => {
3885                    self.render_message(crate::wal::role::SYSTEM, text)
3886                }
3887                _ => Some(Vec::new()),
3888            },
3889            (None, false) => self.render_system_with_tools(system, tool_jsons),
3890        }
3891    }
3892
3893    fn render_prompt_structured_with(
3894        &self,
3895        messages: &[ChatMessage],
3896        tools: &[String],
3897        kwargs: &serde_json::Map<String, serde_json::Value>,
3898    ) -> Option<Vec<u32>> {
3899        let sys_text = messages
3900            .iter()
3901            .find(|m| m.role == crate::wal::role::SYSTEM)
3902            .map(|m| m.content.as_str());
3903        let derived = self
3904            .template
3905            .as_deref()
3906            .and_then(|t| self.derived_system_body(t, sys_text, tools, kwargs));
3907        let Some(body) = derived else {
3908            return self.render_prompt_structured(messages, tools).map(|p| self.reopen_for(p, kwargs));
3909        };
3910        let mut out = self.stream_prologue();
3911        let mut derived_emitted = false;
3912        for m in messages {
3913            if !derived_emitted && m.role == crate::wal::role::SYSTEM {
3914                out.extend(self.turn(crate::wal::role::SYSTEM, &pieces_of(&body)));
3915                derived_emitted = true;
3916            } else {
3917                out.extend(self.render_message(m.role, &m.content)?);
3918            }
3919        }
3920        if !derived_emitted {
3921            let mut with = self.turn(crate::wal::role::SYSTEM, &pieces_of(&body));
3922            with.extend(std::mem::take(&mut out));
3923            out = with;
3924        }
3925        if let Some(op) = self.generation_prefix(TurnState::AfterInput) {
3926            out.extend(op);
3927        }
3928        Some(self.reopen_for(out, kwargs))
3929    }
3930
3931    fn render_tool_result(&self, _name: &str, content: &str) -> Option<Vec<u32>> {
3932        Some(self.turn(
3933            crate::wal::role::USER,
3934            &[
3935                Piece::Framing("<tool_response>\n"),
3936                Piece::Content(content),
3937                Piece::Framing("\n</tool_response>"),
3938            ],
3939        ))
3940    }
3941
3942    fn render_assistant_with_tool_calls(
3943        &self,
3944        content: &str,
3945        calls: &[(String, String)],
3946    ) -> Option<Vec<u32>> {
3947        let turn = self.assistant_tool_turn(content, calls)?;
3948        Some(self.opened(turn, &self.assistant_openers().in_history))
3949    }
3950
3951    fn render_assistant_with_tool_calls_after_query(
3952        &self,
3953        content: &str,
3954        calls: &[(String, String)],
3955    ) -> Option<Vec<u32>> {
3956        let turn = self.assistant_tool_turn(content, calls)?;
3957        Some(self.opened(turn, &self.assistant_openers().last))
3958    }
3959}
3960
3961impl ChatMlCodec {
3962    /// An assistant turn with tool calls, nothing between its role line and
3963    /// its content (the openers go there).
3964    fn assistant_tool_turn(&self, content: &str, calls: &[(String, String)]) -> Option<Vec<u32>> {
3965        let derived = self
3966            .tool_source()
3967            .and_then(|t| Self::derived_call_pieces(t, content, calls));
3968        if let Some(block) = derived {
3969            let sep = if content.is_empty() { "" } else { "\n" };
3970            let mut pieces = vec![Piece::Content(content), Piece::Framing(sep)];
3971            pieces.extend(pieces_of(&block));
3972            return Some(self.turn(crate::wal::role::ASSISTANT, &pieces));
3973        }
3974        let mut span = vec![self.im_start];
3975        span.extend(self.inner.encode_pieces(&[("assistant\n", false), (content, true)]));
3976        for (i, (name, args)) in calls.iter().enumerate() {
3977            if (i == 0 && !content.is_empty()) || i > 0 {
3978                span.push(self.newline);
3979            }
3980            span.push(self.tool_open);
3981            span.extend(self.inner.encode_content(&format!(
3982                "\n{{\"name\": \"{name}\", \"arguments\": {args}}}\n"
3983            )));
3984            span.push(self.tool_close);
3985        }
3986        span.push(self.im_end);
3987        span.push(self.newline);
3988        Some(span)
3989    }
3990}
3991
3992#[cfg(test)]
3993mod tests {
3994    use super::*;
3995
3996    fn switch(template: &str) -> bool {
3997        let mut env = minijinja::Environment::new();
3998        env.add_function("raise_exception", |m: String| -> Result<minijinja::Value, minijinja::Error> {
3999            Err(minijinja::Error::new(minijinja::ErrorKind::InvalidOperation, m))
4000        });
4001        env.add_template("chat", template).unwrap();
4002        let t = env.get_template("chat").unwrap();
4003        thinking_switch_changes(|msgs, tools, on| {
4004            let messages: Vec<_> = msgs
4005                .iter()
4006                .map(|m| {
4007                    let role = if m.role == crate::wal::role::SYSTEM { "system" } else { "user" };
4008                    minijinja::context! { role => role, content => m.content.clone() }
4009                })
4010                .collect();
4011            let tools: Vec<serde_json::Value> = tools.iter().map(|t| serde_json::from_str(t).unwrap()).collect();
4012            t.render(minijinja::context! {
4013                messages => messages,
4014                tools => tools,
4015                add_generation_prompt => true,
4016                enable_thinking => on,
4017            })
4018            .ok()
4019        })
4020    }
4021
4022    const TURNS: &str = "{% for m in messages %}<|im_start|>{{ m.role }}\n{{ m.content }}<|im_end|>\n{% endfor %}";
4023
4024    #[test]
4025    fn a_template_that_branches_on_enable_thinking_has_the_switch() {
4026        let qwen = format!(
4027            "{TURNS}{{% if add_generation_prompt %}}<|im_start|>assistant\n\
4028             {{% if enable_thinking is defined and enable_thinking is false %}}<think>\n\n</think>\n\n{{% endif %}}{{% endif %}}"
4029        );
4030        assert!(switch(&qwen));
4031        let strict = format!("{{% if not enable_thinking %}}{{{{ raise_exception('thinking is required') }}}}{{% endif %}}{TURNS}");
4032        assert!(switch(&strict));
4033        let with_tools = format!("{{% if tools and enable_thinking %}}<think>{{% endif %}}{TURNS}");
4034        assert!(switch(&with_tools));
4035        let with_system =
4036            format!("{{% if messages[0].role == 'system' and enable_thinking %}}<think>{{% endif %}}{TURNS}");
4037        assert!(switch(&with_system));
4038    }
4039
4040    #[test]
4041    fn a_template_that_only_names_enable_thinking_has_no_switch() {
4042        for template in [
4043            format!("{{# enable_thinking is not supported #}}{TURNS}"),
4044            format!("{TURNS}{{{{ 'enable_thinking' if false }}}}"),
4045            format!("{{% set enable_thinking = true %}}{{% if enable_thinking %}}{{% endif %}}{TURNS}"),
4046        ] {
4047            assert!(!switch(&template), "{template}");
4048        }
4049    }
4050
4051    fn parse(raw: &str) -> Option<(String, serde_json::Value)> {
4052        let (n, a) = parse_gemma_tool_call(raw)?;
4053        Some((n, serde_json::from_str(&a).unwrap()))
4054    }
4055
4056    #[test]
4057    fn gemma_call_with_markers_parses() {
4058        let (name, args) = parse("<|tool_call>call:bash{command:ls -R}<tool_call|>").unwrap();
4059        assert_eq!(name, "bash");
4060        assert_eq!(args, serde_json::json!({"command": "ls -R"}));
4061    }
4062
4063    #[test]
4064    fn gemma_call_without_markers_parses() {
4065        let (name, args) = parse("call:bash{command:ls -R}").unwrap();
4066        assert_eq!(name, "bash");
4067        assert_eq!(args, serde_json::json!({"command": "ls -R"}));
4068    }
4069
4070    #[test]
4071    fn gemma_bare_value_may_contain_a_comma() {
4072        let (_, args) = parse("call:grep{pattern:hello, world, path:notes.txt}").unwrap();
4073        assert_eq!(args, serde_json::json!({"pattern": "hello, world", "path": "notes.txt"}));
4074    }
4075
4076    #[test]
4077    fn gemma_scalars_keep_their_types() {
4078        let (_, args) =
4079            parse("call:f{a:true, b:false, c:5, d:1.5, e:null, s:plain text}").unwrap();
4080        assert_eq!(
4081            args,
4082            serde_json::json!({"a": true, "b": false, "c": 5, "d": 1.5, "e": null, "s": "plain text"})
4083        );
4084    }
4085
4086    #[test]
4087    fn gemma_quoted_string_form_parses() {
4088        let (_, args) = parse("call:f{a:<|\"|>x, y} z<|\"|>}").unwrap();
4089        assert_eq!(args, serde_json::json!({"a": "x, y} z"}));
4090    }
4091
4092    #[test]
4093    fn gemma_nested_object_parses() {
4094        let (_, args) = parse("call:f{o:{k:v}}").unwrap();
4095        assert_eq!(args, serde_json::json!({"o": {"k": "v"}}));
4096    }
4097
4098    #[test]
4099    fn gemma_array_elements_split_on_commas() {
4100        let (_, args) = parse("call:f{l:[1, 2, 3]}").unwrap();
4101        assert_eq!(args, serde_json::json!({"l": [1, 2, 3]}));
4102    }
4103
4104    #[test]
4105    fn gemma_array_of_strings_keeps_each_element() {
4106        let (_, args) = parse("call:f{l:[alpha, beta gamma, delta]}").unwrap();
4107        assert_eq!(args, serde_json::json!({"l": ["alpha", "beta gamma", "delta"]}));
4108    }
4109
4110    #[test]
4111    fn gemma_object_value_may_still_contain_a_comma() {
4112        let (_, args) = parse("call:f{pattern:hello, world, path:n.txt}").unwrap();
4113        assert_eq!(args, serde_json::json!({"pattern": "hello, world", "path": "n.txt"}));
4114    }
4115
4116    #[test]
4117    fn gemma_integers_stay_integers() {
4118        let (_, args) = parse("call:f{n:42}").unwrap();
4119        assert_eq!(args, serde_json::json!({"n": 42}));
4120        assert_eq!(serde_json::to_string(&args).unwrap(), r#"{"n":42}"#);
4121    }
4122
4123    #[test]
4124    fn gemma_no_args_is_an_empty_object_not_a_failure() {
4125        let (name, args) = parse("call:now{}").unwrap();
4126        assert_eq!(name, "now");
4127        assert_eq!(args, serde_json::json!({}));
4128    }
4129
4130    #[test]
4131    fn gemma_rejects_what_is_not_a_call() {
4132        assert!(parse_gemma_tool_call("I will run ls -R for you.").is_none());
4133        assert!(parse_gemma_tool_call(r#"{"name":"bash","arguments":{}}"#).is_none());
4134        assert!(parse_gemma_tool_call("call:bash").is_none());
4135        assert!(parse_gemma_tool_call("call:{command:ls}").is_none());
4136    }
4137
4138    #[test]
4139    fn gemma_truncated_body_still_names_the_call() {
4140        let (name, args) = parse("<|tool_call>call:bash{command:ls -R").unwrap();
4141        assert_eq!(name, "bash");
4142        assert_eq!(args, serde_json::json!({"command": "ls -R"}));
4143    }
4144
4145    #[test]
4146    fn gemma_call_grammar_orders_arguments_as_the_template_renders_them() {
4147        let params = serde_json::json!({"type": "object",
4148            "properties": {"pattern": {"type": "string"}, "path": {"type": "string"}},
4149            "required": ["pattern", "path"]});
4150        let body = gemma_call_body_format("grep", &params);
4151        let keys: Vec<String> = body["elements"]
4152            .as_array()
4153            .unwrap()
4154            .iter()
4155            .filter_map(|e| e["elements"][0]["value"].as_str().map(String::from))
4156            .collect();
4157        assert_eq!(keys, vec!["path:", "pattern:"], "{body}");
4158    }
4159
4160    #[test]
4161    fn gemma_envelope_is_selected_by_name_and_round_trips() {
4162        let e = ToolEnvelope::from_name("gemma").expect("gemma envelope available");
4163        assert_eq!(e.name(), "gemma");
4164        assert!(ToolEnvelope::names().contains(&"gemma"));
4165        let (name, _) = e.parse_call("<|tool_call>call:bash{command:ls}<tool_call|>", None).unwrap();
4166        assert_eq!(name, "bash");
4167        assert!(e.call_grammar(&[r#"{"function":{"name":"bash"}}"#.to_string()]).is_none());
4168    }
4169
4170    #[test]
4171    fn every_structural_tag_is_a_structural_tag_document() {
4172        let tool = r#"{"name":"f","parameters":{"type":"object","properties":{"a":{"type":"string"}},"required":["a"]}}"#.to_string();
4173                let envelopes = vec![ToolEnvelope::Json, ToolEnvelope::Gemma, ToolEnvelope::Atem];
4174        for e in envelopes {
4175            let Some(tag) =
4176                e.structural_tag(std::slice::from_ref(&tool), "<|tool_call>", "<tool_call|>", true)
4177            else {
4178                continue;
4179            };
4180            let v: serde_json::Value = serde_json::from_str(&tag).expect("tag is JSON");
4181            assert_eq!(
4182                v.get("type").and_then(|t| t.as_str()),
4183                Some("structural_tag"),
4184                "{} tag root type: {tag}",
4185                e.name()
4186            );
4187            assert!(
4188                v.get("format").is_some(),
4189                "{} tag has no `format` field — xgrammar rejects such a document, and the caller then decodes unconstrained: {tag}",
4190                e.name()
4191            );
4192        }
4193    }
4194
4195    #[test]
4196    fn gemma_structural_tag_frames_the_call_without_json() {
4197        let tool = r#"{"name":"grep","parameters":{"type":"object","properties":{"pattern":{"type":"string"},"n":{"type":"integer"},"deep":{"type":"boolean"}},"required":["pattern","n","deep"]}}"#.to_string();
4198        let tag = ToolEnvelope::Gemma
4199            .structural_tag(&[tool], "<|tool_call>", "<tool_call|>", true)
4200            .expect("gemma tag");
4201        let v: serde_json::Value = serde_json::from_str(&tag).unwrap();
4202        let f = &v["format"];
4203        assert_eq!(f["type"], "triggered_tags");
4204        assert_eq!(f["at_least_one"], true, "tool_choice=required must reach the tag");
4205        assert_eq!(f["stop_after_first"], true, "a forced call is one call");
4206        assert_eq!(f["triggers"][0], "<|tool_call>");
4207        let content = &f["tags"][0]["content"];
4208        assert_eq!(content["type"], "sequence");
4209        let dump = content.to_string();
4210        assert!(dump.contains(r#""value":"call:grep{""#), "call prefix: {dump}");
4211        assert!(!dump.contains("json_schema"), "gemma body must not be JSON: {dump}");
4212        assert!(dump.contains(r#""value":"n:""#), "integer key framed: {dump}");
4213        assert!(dump.contains("-?[0-9]+"), "integer value pattern: {dump}");
4214        assert!(dump.contains(r#""value":"true""#), "boolean rendered as const: {dump}");
4215    }
4216
4217    #[test]
4218    fn a_dialect_without_terminators_is_unaffected() {
4219        assert!(MockChatCodec.turn_terminators().is_empty());
4220    }
4221
4222    #[test]
4223    fn json_envelope_still_rejects_the_gemma_body() {
4224        assert!(parse_json_tool_call("call:bash{command:ls -R}").is_none());
4225    }
4226
4227    #[test]
4228    fn utf8_stream_reassembles_split_characters() {
4229        let mut s = Utf8Stream::default();
4230        assert_eq!(s.push(b"caf"), "caf");
4231        assert_eq!(s.push(&[0xC3]), "");
4232        assert_eq!(s.push(&[0xA9]), "\u{e9}");
4233        assert_eq!(s.flush(), "");
4234    }
4235
4236    #[test]
4237    fn utf8_stream_replaces_invalid_and_flushes_partial() {
4238        let mut s = Utf8Stream::default();
4239        assert_eq!(s.push(&[0xFF, b'a']), "\u{FFFD}a");
4240        assert_eq!(s.push(&[0xE2, 0x82]), "");
4241        assert_eq!(s.flush(), "\u{FFFD}");
4242    }
4243
4244    fn shape(runs: &[Run]) -> Vec<(Vec<u32>, u32, bool)> {
4245        runs.iter()
4246            .map(|r| (r.span.clone(), r.channel, r.closes))
4247            .collect()
4248    }
4249
4250    #[test]
4251    fn segmenter_partitions_at_markers_across_slices() {
4252        let think = (100, 101, crate::wal::channel::REASONING);
4253        let tool = (200, 201, crate::wal::channel::TOOL_CALL);
4254        let mut seg = Segmenter::new(vec![think, tool]);
4255        let runs = seg.split(&[1, 2, 100, 3]);
4256        assert_eq!(
4257            shape(&runs),
4258            vec![
4259                (vec![1, 2], crate::wal::channel::TEXT, false),
4260                (vec![100, 3], crate::wal::channel::REASONING, false),
4261            ]
4262        );
4263        let runs = seg.split(&[4, 101, 200, 5, 201, 6]);
4264        assert_eq!(
4265            shape(&runs),
4266            vec![
4267                (vec![4, 101], crate::wal::channel::REASONING, true),
4268                (vec![200, 5, 201], crate::wal::channel::TOOL_CALL, true),
4269                (vec![6], crate::wal::channel::TEXT, false),
4270            ]
4271        );
4272        let input = vec![100, 200, 7, 101, 8];
4273        let runs = seg.split(&input);
4274        let concat: Vec<u32> = runs.iter().flat_map(|r| r.span.clone()).collect();
4275        assert_eq!(concat, input);
4276        assert_eq!(runs[0].channel, crate::wal::channel::REASONING);
4277    }
4278
4279    #[test]
4280    fn a_named_block_keeps_its_name_out_of_the_text() {
4281        use crate::wal::channel;
4282        const OPEN: u32 = 100;
4283        const CLOSE: u32 = 101;
4284        let (thought, nl) = (50, 51);
4285        let named = || Segmenter::new(vec![(OPEN, CLOSE, channel::REASONING)]).with_names(vec![(OPEN, vec![thought, nl])]);
4286        let texts = |runs: Vec<Run>| -> Vec<(u32, Vec<u32>, bool)> { runs.into_iter().map(|r| (r.channel, r.text, r.closes)).collect() };
4287
4288        let whole = [OPEN, thought, nl, 7, 8, CLOSE, 9];
4289        let runs = named().split(&whole);
4290        assert_eq!(runs.iter().flat_map(|r| r.span.clone()).collect::<Vec<_>>(), whole, "spans partition the stream");
4291        assert_eq!(texts(runs), vec![(channel::REASONING, vec![7, 8], true), (channel::TEXT, vec![9], false)]);
4292
4293        let mut seg = named();
4294        let mut runs = seg.split(&[OPEN, thought]);
4295        runs.extend(seg.split(&[nl, 7, CLOSE]));
4296        let reasoning: Vec<u32> = runs.iter().filter(|r| r.channel == channel::REASONING).flat_map(|r| r.text.clone()).collect();
4297        assert_eq!(reasoning, vec![7]);
4298
4299        let runs = named().split(&[OPEN, thought, 7, nl, CLOSE]);
4300        assert_eq!(texts(runs), vec![(channel::REASONING, vec![7, nl], true)]);
4301
4302        let mut seg = named();
4303        seg.split(&[OPEN, thought, nl, 7, CLOSE]);
4304        assert_eq!(texts(seg.split(&[OPEN, thought, nl, 8, CLOSE])), vec![(channel::REASONING, vec![8], true)]);
4305        let mut primed = named();
4306        primed.prime(channel::REASONING);
4307        assert_eq!(texts(primed.split(&[thought, CLOSE])), vec![(channel::REASONING, vec![thought], true)]);
4308    }
4309
4310    #[test]
4311    fn segmenter_without_markers_is_single_channel() {
4312        let mut seg = Segmenter::new(Vec::new());
4313        let runs = seg.split(&[1, 2, 3]);
4314        assert_eq!(
4315            shape(&runs),
4316            vec![(vec![1, 2, 3], crate::wal::channel::TEXT, false)]
4317        );
4318    }
4319
4320    #[test]
4321    fn mock_codec_round_trips_multibyte_text() {
4322        let c = MockCodec;
4323        let text = "héllo — ok";
4324        let toks = c.encode(text);
4325        assert_eq!(c.decode(&toks), text);
4326    }
4327}
4328
4329#[cfg(test)]
4330mod learned_tool_wire_tests {
4331    use super::{learn_delims, S_K1, S_K2, S_NAME, S_V1, S_V2};
4332
4333    fn render(name_open: &str, name_close: &str, key_open: &str, key_close: &str, val_close: &str, args: &[(&str, &str)]) -> String {
4334        let mut s = String::from(name_open);
4335        s.push_str(S_NAME);
4336        s.push_str(name_close);
4337        for (k, v) in args {
4338            s.push_str(key_open);
4339            s.push_str(k);
4340            s.push_str(key_close);
4341            s.push_str(v);
4342            s.push_str(val_close);
4343        }
4344        s
4345    }
4346
4347    #[test]
4348    fn learns_a_qwen_coder_xml_body_and_inverts_it() {
4349        let frame = ("<tool_call>", "</tool_call>");
4350        let two = render("<function=", ">\n", "<parameter=", ">\n", "\n</parameter>\n", &[(S_K1, S_V1), (S_K2, S_V2)]) + "</function>";
4351        let zero = render("<function=", ">\n", "", "", "", &[]) + "</function>";
4352        let d = learn_delims(&two, Some(&(zero)), Some(frame)).expect("learns");
4353        let raw = "<function=bash>\n<parameter=command>\nls -la /tmp\n</parameter>\n<parameter=timeout>\n30\n</parameter>\n</function>";
4354        let (name, args) = d.parse(raw, None).expect("parses");
4355        assert_eq!(name, "bash");
4356        let v: serde_json::Value = serde_json::from_str(&args).unwrap();
4357        assert_eq!(v["command"], "ls -la /tmp");
4358        assert_eq!(v["timeout"], 30);
4359        let cut = "<function=read>\n<parameter=path>\n/a\n</parameter>\n<parameter=limit>\n5";
4360        let (n2, a2) = d.parse(cut, None).unwrap();
4361        assert_eq!(n2, "read");
4362        let v2: serde_json::Value = serde_json::from_str(&a2).unwrap();
4363        assert_eq!(v2["path"], "/a");
4364    }
4365
4366    #[test]
4367    fn learns_a_zero_arg_call_terminator() {
4368        let two = render("<function=", ">\n", "<parameter=", ">\n", "\n</parameter>\n", &[(S_K1, S_V1), (S_K2, S_V2)]) + "</function>";
4369        let zero = render("<function=", ">\n", "", "", "", &[]) + "</function>";
4370        let d = learn_delims(&two, Some(&zero), Some(("<tool_call>", "</tool_call>"))).unwrap();
4371        let (name, args) = d.parse("<function=now>\n</function>", None).expect("zero-arg call");
4372        assert_eq!(name, "now");
4373        assert_eq!(args, "{}");
4374    }
4375
4376    fn qwen_delims() -> super::Delims {
4377        let two = render("<function=", ">\n", "<parameter=", ">\n", "\n</parameter>\n", &[(S_K1, S_V1), (S_K2, S_V2)]) + "</function>";
4378        let zero = render("<function=", ">\n", "", "", "", &[]) + "</function>";
4379        learn_delims(&two, Some(&zero), Some(("<tool_call>", "</tool_call>"))).expect("learns")
4380    }
4381
4382    fn serve_schemas() -> super::ToolSchemas {
4383        super::ToolSchemas::from_values(
4384            serde_json::json!([{"type": "function", "function": {"name": "serve", "parameters": {
4385                "type": "object",
4386                "properties": {
4387                    "port": {"type": "string"}, "host": {"type": "string"},
4388                    "workers": {"type": "integer"}, "ratio": {"type": "number"},
4389                    "debug": {"type": "boolean"}, "verbose": {"type": "boolean"},
4390                    "tags": {"type": "array", "items": {"type": "string"}},
4391                    "env": {"type": "object"},
4392                    "id": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
4393                    "note": {"type": ["string", "null"]},
4394                    "limit": {"$ref": "#/$defs/Count"},
4395                    "mode": {"enum": ["1", "2"]},
4396                    "loose": {}
4397                },
4398                "$defs": {"Count": {"type": "integer"}}
4399            }}}])
4400            .as_array()
4401            .unwrap(),
4402        )
4403    }
4404
4405    fn parsed(d: &super::Delims, raw: &str, schemas: Option<&super::ToolSchemas>) -> serde_json::Value {
4406        let (name, args) = d.parse(raw, schemas).expect("parses");
4407        assert_eq!(name, "serve");
4408        serde_json::from_str(&args).unwrap()
4409    }
4410
4411    #[test]
4412    fn learned_xml_wire_types_values_from_the_declared_schema() {
4413        let d = qwen_delims();
4414        let s = serve_schemas();
4415        let raw = "<function=serve>\n\
4416            <parameter=port>\n8080\n</parameter>\n\
4417            <parameter=host>\nnull\n</parameter>\n\
4418            <parameter=workers>\n4\n</parameter>\n\
4419            <parameter=ratio>\n0.5\n</parameter>\n\
4420            <parameter=debug>\ntrue\n</parameter>\n\
4421            <parameter=verbose>\nFalse\n</parameter>\n\
4422            <parameter=tags>\n[\"a\", \"b c\"]\n</parameter>\n\
4423            <parameter=env>\n{\"K\": 1}\n</parameter>\n\
4424            <parameter=id>\n42\n</parameter>\n\
4425            <parameter=note>\nnull\n</parameter>\n\
4426            <parameter=limit>\n10\n</parameter>\n\
4427            <parameter=mode>\n2\n</parameter>\n\
4428            <parameter=loose>\n7\n</parameter>\n\
4429            <parameter=undeclared>\ntrue\n</parameter>\n\
4430            </function>";
4431        assert_eq!(
4432            parsed(&d, raw, Some(&s)),
4433            serde_json::json!({
4434                "port": "8080", "host": "null",
4435                "workers": 4, "ratio": 0.5, "debug": true, "verbose": false,
4436                "tags": ["a", "b c"], "env": {"K": 1},
4437                "id": "42", "note": "null",
4438                "limit": 10, "mode": "2",
4439                "loose": 7, "undeclared": true
4440            })
4441        );
4442        let odd = parsed(&d, "<function=serve>\n<parameter=workers>\nfour\n</parameter>\n</function>", Some(&s));
4443        assert_eq!(odd, serde_json::json!({"workers": "four"}));
4444    }
4445
4446    #[test]
4447    fn learned_xml_wire_without_a_schema_keeps_the_heuristic() {
4448        let d = qwen_delims();
4449        let raw = "<function=serve>\n<parameter=port>\n8080\n</parameter>\n<parameter=host>\nlocalhost\n</parameter>\n</function>";
4450        assert_eq!(parsed(&d, raw, None), serde_json::json!({"port": 8080, "host": "localhost"}));
4451        let other = super::ToolSchemas::from_values(
4452            serde_json::json!([{"name": "other", "parameters": {"properties": {"port": {"type": "string"}}}}])
4453                .as_array()
4454                .unwrap(),
4455        );
4456        assert_eq!(parsed(&d, raw, Some(&other)), serde_json::json!({"port": 8080, "host": "localhost"}));
4457    }
4458
4459    #[test]
4460    fn learned_xml_wire_keeps_payload_whitespace() {
4461        let d = qwen_delims();
4462        let s = super::ToolSchemas::from_values(
4463            serde_json::json!([{"name": "write", "parameters": {"properties": {
4464                "path": {"type": "string"}, "content": {"type": "string"}
4465            }}}])
4466            .as_array()
4467            .unwrap(),
4468        );
4469        let raw = "<function=write>\n<parameter=path>\n/tmp/a.py\n</parameter>\n\
4470                   <parameter=content>\n    def f():\n        return 1\n\n</parameter>\n</function>";
4471        for schemas in [Some(&s), None] {
4472            let (_, args) = d.parse(raw, schemas).unwrap();
4473            let v: serde_json::Value = serde_json::from_str(&args).unwrap();
4474            assert_eq!(v["path"], "/tmp/a.py");
4475            assert_eq!(v["content"], "    def f():\n        return 1\n", "schemas={}", schemas.is_some());
4476        }
4477    }
4478
4479    #[test]
4480    fn closed_args_type_from_the_same_schema() {
4481        let d = qwen_delims();
4482        let s = serve_schemas();
4483        let raw = "<function=serve>\n<parameter=port>\n8080\n</parameter>\n<parameter=workers>\n4\n</parameter>\n<parameter=id>\n9";
4484        let mut cursor = None;
4485        let got = d.closed_args(raw, &mut cursor, Some(&s));
4486        assert_eq!(
4487            got,
4488            vec![("port".to_string(), serde_json::json!("8080")), ("workers".to_string(), serde_json::json!(4))]
4489        );
4490        let mut cursor = None;
4491        assert_eq!(d.closed_args(raw, &mut cursor, None)[0].1, serde_json::json!(8080));
4492    }
4493
4494    #[cfg(feature = "basert")]
4495    #[test]
4496    fn learned_xml_wire_forced_tag_is_one_call_of_free_text_values() {
4497        let d = qwen_delims();
4498        let tool = serde_json::json!({"type": "function", "function": {"name": "write", "parameters": {
4499            "type": "object",
4500            "properties": {"content": {"type": "string"}, "mode": {"type": "integer"}},
4501            "required": ["content"]
4502        }}})
4503        .to_string();
4504        let tag: serde_json::Value = serde_json::from_str(
4505            &d.structural_tag(std::slice::from_ref(&tool), "<tool_call>", "</tool_call>", true).expect("forced tag"),
4506        )
4507        .unwrap();
4508        let f = &tag["format"];
4509        assert_eq!(f["at_least_one"], true);
4510        assert_eq!(f["stop_after_first"], true);
4511        let el = f["tags"][0]["content"]["elements"].as_array().unwrap();
4512        assert_eq!(el[1]["value"], "<function=write>\n<");
4513        let content = &el[2];
4514        assert_eq!(
4515            content,
4516            &serde_json::json!({
4517                "type": "tag", "begin": "parameter=content>\n",
4518                "content": {"type": "any_text"}, "end": "\n</parameter>\n<"
4519            })
4520        );
4521        let mode = &el[3]["content"];
4522        assert_eq!(el[3]["type"], "optional");
4523        assert_eq!(mode["begin"], "parameter=mode>\n");
4524        assert_eq!(mode["content"]["type"], "json_schema");
4525        assert_eq!(mode["content"]["json_schema"]["type"], "integer");
4526        assert_eq!(el.last().unwrap(), &serde_json::json!({"type": "any_text"}));
4527    }
4528
4529    #[test]
4530    fn bails_when_the_body_has_no_repeating_key_opener() {
4531        let two = format!("call:{S_NAME}{{{S_K1}:{S_V1},{S_K2}:{S_V2}}}");
4532        assert!(learn_delims(&two, None, Some(("<|tool_call>", "<tool_call|>"))).is_none());
4533    }
4534}