winx_code_agent/utils/
encoder.rs1use std::sync::OnceLock;
10
11use tokie::Tokenizer;
12
13static CLAUDE_TOKENIZER_JSON: &[u8] =
15 include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/claude-tokenizer.json"));
16
17fn tokenizer() -> Option<&'static Tokenizer> {
18 static TOKENIZER: OnceLock<Option<Tokenizer>> = OnceLock::new();
19 TOKENIZER
20 .get_or_init(|| {
21 let json = match std::str::from_utf8(CLAUDE_TOKENIZER_JSON) {
22 Ok(json) => json,
23 Err(error) => {
24 tracing::warn!("Embedded Claude tokenizer is not UTF-8: {error}");
25 return None;
26 }
27 };
28 match tokie::hf::from_json_str(json) {
29 Ok(tokenizer) => Some(tokenizer),
30 Err(error) => {
31 tracing::warn!(
32 "Failed to load embedded Claude tokenizer, using estimate: {error}"
33 );
34 None
35 }
36 }
37 })
38 .as_ref()
39}
40
41pub fn count_tokens(text: &str) -> usize {
43 match encode_ids(text) {
44 Some(ids) => ids.len(),
45 None => estimate_tokens(text),
46 }
47}
48
49pub fn encode_ids(text: &str) -> Option<Vec<u32>> {
52 let tokenizer = tokenizer()?;
53 Some(tokenizer.encode_ids(text, false))
54}
55
56pub fn decode_ids(ids: &[u32]) -> Option<String> {
58 let tokenizer = tokenizer()?;
59 tokenizer.decode(ids)
60}
61
62pub fn definitely_fits_token_budget(text: &str, max_tokens: usize) -> bool {
69 text.len() <= max_tokens
70}
71
72pub fn budget_from_env(var: &str, default: usize) -> usize {
77 std::env::var(var)
78 .ok()
79 .and_then(|value| value.trim().parse::<usize>().ok())
80 .filter(|&n| n > 0)
81 .unwrap_or(default)
82}
83
84pub fn estimate_tokens(text: &str) -> usize {
86 text.chars().count().div_ceil(4).max(text.split_whitespace().count())
87}
88
89#[cfg(test)]
90mod budget_tests {
91 use super::budget_from_env;
92
93 #[test]
94 fn falls_back_to_default_when_unset_or_invalid() {
95 assert_eq!(budget_from_env("WINX_DEFINITELY_UNSET_BUDGET_XYZ", 24_000), 24_000);
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn counts_tokens_for_simple_text() {
106 assert!(count_tokens("hello world") >= 1);
108 assert_eq!(count_tokens(""), 0);
109 }
110
111 #[test]
112 fn estimate_is_nonzero_for_words() {
113 assert!(estimate_tokens("a b c d") >= 4);
114 }
115
116 #[test]
117 fn byte_length_is_a_safe_token_upper_bound() {
118 for sample in [
119 "plain English prose",
120 "fn main() { println!(\"hello\"); }",
121 "áéíóú 日本語 🚀",
122 "symbols: !@#$%^&*()[]{}",
123 "line one\nline two\nline three",
124 ] {
125 let ids = encode_ids(sample);
126 assert!(
127 ids.as_ref().is_some_and(|ids| ids.len() <= sample.len()),
128 "sample produced more tokens than UTF-8 bytes"
129 );
130 }
131 }
132
133 #[test]
134 fn embedded_claude_tokenizer_matches_golden_ids() -> anyhow::Result<()> {
135 let cases: &[(&str, &[u32])] = &[
136 ("", &[]),
137 ("hello world", &[9381, 2253]),
138 (
139 "fn main() { println!(\"hello\"); }",
140 &[3258, 1890, 370, 503, 637, 9706, 5, 496, 9381, 5018, 863],
141 ),
142 (
143 "áéíóú 日本語 🚀",
144 &[2273, 1222, 2843, 3024, 8286, 225, 12956, 12163, 23598, 257, 41270, 253, 227],
145 ),
146 ("line one\nline two\nline three", &[936, 813, 203, 936, 1231, 203, 936, 2119]),
147 ];
148 for (sample, expected) in cases {
149 let actual = encode_ids(sample)
150 .ok_or_else(|| anyhow::anyhow!("embedded tokenizer failed for {sample:?}"))?;
151 assert_eq!(&actual, expected, "token ids diverged for {sample:?}");
152 assert_eq!(decode_ids(&actual).as_deref(), Some(*sample));
153 }
154 Ok(())
155 }
156
157 #[test]
158 fn byte_upper_bound_only_accepts_payloads_proven_to_fit() {
159 assert!(definitely_fits_token_budget("four", 4));
160 assert!(!definitely_fits_token_budget("five!", 4));
161 assert!(!definitely_fits_token_budget("é", 1));
162 assert!(definitely_fits_token_budget("é", 2));
163 }
164}