use serde_json::Value;
use crate::wire;
#[cfg(test)]
mod tests;
pub(crate) fn estimate_tokens(bytes: u64) -> i32 {
let tokens = (2 * u128::from(bytes)).div_ceil(7);
i32::try_from(tokens).unwrap_or(i32::MAX)
}
fn json_tokens(value: &Value) -> i32 {
estimate_tokens(byte_count(wire::stringify(value).len()))
}
fn byte_count(len: usize) -> u64 {
u64::try_from(len).unwrap_or(u64::MAX)
}
pub(crate) fn estimate_input_tokens(arguments: Option<&Value>) -> Option<i32> {
match arguments {
None | Some(Value::Null) => None,
Some(value) => Some(json_tokens(value)),
}
}
pub(crate) fn estimate_output_tokens(response: Option<&Value>) -> Option<i32> {
let response = match response {
None | Some(Value::Null) => return None,
Some(value) => value,
};
let Some(content) = response.get("content").and_then(Value::as_array) else {
return Some(json_tokens(response));
};
if content.is_empty() {
return Some(match response.get("structuredContent") {
None | Some(Value::Null) => 0,
Some(structured) => json_tokens(structured),
});
}
let bytes: u64 = content.iter().map(block_bytes).sum();
Some(estimate_tokens(bytes))
}
fn block_bytes(block: &Value) -> u64 {
let text = match block.get("type").and_then(Value::as_str) {
Some("text") => block.get("text"),
Some("resource") => block.get("resource").and_then(|r| r.get("text")),
_ => None,
};
text.and_then(Value::as_str)
.map_or(0, |text| byte_count(text.len()))
}