Skip to main content

kernel/capabilities/
mod.rs

1//! Streaming text processors for model output: separating thinking spans from
2//! visible text, and detecting stop sequences. Both hold back a partial-tag
3//! suffix so a delimiter split across chunk boundaries is still recognized.
4
5pub mod chat;
6pub mod chunk;
7pub mod stop_matcher;
8pub mod think_splitter;
9pub mod tools;
10
11pub use chat::{
12    AttachmentKind, ChatAttachment, ChatMessage, ChatMlPrompt, ChatRole, ChatWireError,
13    decode_tool_specs,
14};
15pub use chunk::{AudioFrame, CapabilityChunk, GenerationStats};
16pub use stop_matcher::{StopMatcher, stop_strings};
17pub use think_splitter::{Piece, TagPair, ThinkSplitter, has_visible_tags};
18pub use tools::{ToolCall, ToolSpec};
19
20/// The byte index at which the last `chars_from_end` characters of `text` begin.
21/// Defined for `chars_from_end >= 1`; returns 0 when `text` has fewer characters
22/// than requested.
23pub(crate) fn char_boundary_from_end(text: &str, chars_from_end: usize) -> usize {
24    if chars_from_end == 0 {
25        return text.len();
26    }
27    text.char_indices()
28        .rev()
29        .nth(chars_from_end - 1)
30        .map_or(0, |(index, _)| index)
31}
32
33/// The byte length of the longest suffix of `text` (up to one character shy of
34/// the longest candidate) that is a prefix of some candidate delimiter. This is
35/// the tail held back until the next chunk can confirm or deny a split delimiter.
36pub(crate) fn held_suffix_len(text: &str, candidates: &[String]) -> usize {
37    let max_chars = candidates
38        .iter()
39        .map(|candidate| candidate.chars().count())
40        .max()
41        .unwrap_or(1);
42    let cap = max_chars.saturating_sub(1).min(text.chars().count());
43    for length in (1..=cap).rev() {
44        let start = char_boundary_from_end(text, length);
45        let suffix = &text[start..];
46        if candidates
47            .iter()
48            .any(|candidate| candidate.starts_with(suffix))
49        {
50            return text.len() - start;
51        }
52    }
53    0
54}