winx_code_agent/utils/
encoder.rs1use std::sync::OnceLock;
10use tokenizers::Tokenizer;
11
12static CLAUDE_TOKENIZER_JSON: &[u8] =
14 include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/claude-tokenizer.json"));
15
16fn tokenizer() -> Option<&'static Tokenizer> {
17 static TOKENIZER: OnceLock<Option<Tokenizer>> = OnceLock::new();
18 TOKENIZER
19 .get_or_init(|| match Tokenizer::from_bytes(CLAUDE_TOKENIZER_JSON) {
20 Ok(tokenizer) => Some(tokenizer),
21 Err(error) => {
22 tracing::warn!("Failed to load embedded Claude tokenizer, using estimate: {error}");
23 None
24 }
25 })
26 .as_ref()
27}
28
29pub fn count_tokens(text: &str) -> usize {
31 match encode_ids(text) {
32 Some(ids) => ids.len(),
33 None => estimate_tokens(text),
34 }
35}
36
37pub fn encode_ids(text: &str) -> Option<Vec<u32>> {
40 let tokenizer = tokenizer()?;
41 tokenizer.encode(text, false).ok().map(|encoding| encoding.get_ids().to_vec())
42}
43
44pub fn decode_ids(ids: &[u32]) -> Option<String> {
46 let tokenizer = tokenizer()?;
47 tokenizer.decode(ids, false).ok()
48}
49
50pub fn definitely_fits_token_budget(text: &str, max_tokens: usize) -> bool {
57 text.len() <= max_tokens
58}
59
60pub fn budget_from_env(var: &str, default: usize) -> usize {
65 std::env::var(var)
66 .ok()
67 .and_then(|value| value.trim().parse::<usize>().ok())
68 .filter(|&n| n > 0)
69 .unwrap_or(default)
70}
71
72pub fn estimate_tokens(text: &str) -> usize {
74 text.chars().count().div_ceil(4).max(text.split_whitespace().count())
75}
76
77#[cfg(test)]
78mod budget_tests {
79 use super::budget_from_env;
80
81 #[test]
82 fn falls_back_to_default_when_unset_or_invalid() {
83 assert_eq!(budget_from_env("WINX_DEFINITELY_UNSET_BUDGET_XYZ", 24_000), 24_000);
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn counts_tokens_for_simple_text() {
94 assert!(count_tokens("hello world") >= 1);
96 assert_eq!(count_tokens(""), 0);
97 }
98
99 #[test]
100 fn estimate_is_nonzero_for_words() {
101 assert!(estimate_tokens("a b c d") >= 4);
102 }
103
104 #[test]
105 fn byte_length_is_a_safe_token_upper_bound() {
106 for sample in [
107 "plain English prose",
108 "fn main() { println!(\"hello\"); }",
109 "áéíóú 日本語 🚀",
110 "symbols: !@#$%^&*()[]{}",
111 "line one\nline two\nline three",
112 ] {
113 let ids = encode_ids(sample);
114 assert!(
115 ids.as_ref().is_some_and(|ids| ids.len() <= sample.len()),
116 "sample produced more tokens than UTF-8 bytes"
117 );
118 }
119 }
120
121 #[test]
122 fn byte_upper_bound_only_accepts_payloads_proven_to_fit() {
123 assert!(definitely_fits_token_budget("four", 4));
124 assert!(!definitely_fits_token_budget("five!", 4));
125 assert!(!definitely_fits_token_budget("é", 1));
126 assert!(definitely_fits_token_budget("é", 2));
127 }
128}