use crate::{
config::CompactionTimingSettings,
providers::{MessageRole, ProviderConversationItem},
typesafe::{JevAnswer, JevQuestion, NoulCriteriaOwned, TypeSafeClient},
};
use std::collections::BTreeMap;
const UNRELATED_WORK: &str = "starts_unrelated_work";
const RECENT_ITEMS: usize = 12;
const MAX_RECENT_CONTEXT_BYTES: usize = 16 * 1024;
pub(crate) fn soft_threshold_tokens(
settings: &CompactionTimingSettings,
max_tokens: usize,
) -> usize {
max_tokens.saturating_mul(usize::from(settings.soft_threshold_percent)) / 100
}
pub(crate) fn unrelated_work_probability(
prompt: &str,
conversation: &[&ProviderConversationItem],
) -> anyhow::Result<f64> {
let state = serde_json::json!({
"recent_conversation": recent_context(conversation),
"new_request": prompt,
});
let questions = BTreeMap::from([(
UNRELATED_WORK.to_string(),
JevQuestion::Noul {
instructions: serde_json::json!({
"question": "Does `new_request` begin independent work that needs none of the prior task-specific details in `recent_conversation`?",
"rules": ["Judge dependency, not shared repository or vocabulary.", "When history is insufficient or the request refers back ambiguously, do not infer independence."]
}),
criteria: Some(NoulCriteriaOwned {
yes: serde_json::json!({"what": "Independent task; no dependency on earlier work.", "examples": ["Explicit change of topic", "A separate task in the same repository"]}),
no: serde_json::json!({"what": "Continues or may depend on earlier work.", "examples": ["Continue", "Apply that fix elsewhere", "Why did that test fail?"]}),
}),
},
)]);
let mut result = TypeSafeClient::from_environment()?.ask_many(&state, questions)?;
match result.value.remove(UNRELATED_WORK) {
Some(JevAnswer::Noul { noul }) if (0.0..=1.0).contains(&noul) => Ok(noul),
_ => anyhow::bail!("Jev compaction timing returned invalid {UNRELATED_WORK} answer"),
}
}
fn recent_context(conversation: &[&ProviderConversationItem]) -> serde_json::Value {
let history = &conversation[..conversation.len().saturating_sub(1)];
let mut messages = history
.iter()
.copied()
.filter(|item| {
matches!(item,
ProviderConversationItem::Message(message)
if matches!(message.role, MessageRole::User | MessageRole::Assistant)
)
})
.rev()
.take(RECENT_ITEMS)
.collect::<Vec<_>>();
messages.reverse();
crate::typesafe::evidence::conversation_evidence(messages, MAX_RECENT_CONTEXT_BYTES)
}