use crate::{ContextError, ContextErrorCode};
pub const PROMPT_SEPARATOR: &str = "\n\n";
pub const TRUNCATION_MARKER: &str = "[truncated]";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PromptBudget {
max_bytes: usize,
max_estimated_tokens: usize,
}
impl PromptBudget {
pub fn new(max_bytes: usize, max_estimated_tokens: usize) -> Result<Self, ContextError> {
if max_bytes == 0
|| max_bytes > 16 * 1024 * 1024
|| max_estimated_tokens == 0
|| u64::try_from(max_estimated_tokens)
.map_or(true, |value| value > tea_protocol::MAX_SAFE_INTEGER)
{
return Err(ContextError::new(
ContextErrorCode::InvalidValue,
"prompt budget is invalid",
));
}
Ok(Self {
max_bytes,
max_estimated_tokens,
})
}
#[must_use]
pub const fn max_bytes(self) -> usize {
self.max_bytes
}
#[must_use]
pub const fn max_estimated_tokens(self) -> usize {
self.max_estimated_tokens
}
}
#[must_use]
pub const fn estimate_tokens(bytes: usize) -> usize {
bytes.saturating_add(2) / 3
}
pub(crate) fn effective_remaining_bytes(budget: PromptBudget, used_bytes: usize) -> usize {
let byte_remaining = budget.max_bytes.saturating_sub(used_bytes);
let token_capacity = budget
.max_estimated_tokens
.saturating_mul(3)
.saturating_sub(used_bytes);
byte_remaining.min(token_capacity)
}
pub(crate) fn truncate(content: &str, maximum: usize) -> Option<String> {
if maximum < TRUNCATION_MARKER.len() {
return None;
}
let content_limit = maximum - TRUNCATION_MARKER.len();
let mut boundary = content_limit.min(content.len());
while boundary > 0 && !content.is_char_boundary(boundary) {
boundary -= 1;
}
let mut output = content[..boundary].to_owned();
output.push_str(TRUNCATION_MARKER);
Some(output)
}