use super::config::*;
use crate::types::*;
use tokio::sync::mpsc;
pub(super) fn default_convert_to_llm(messages: &[AgentMessage]) -> Vec<Message> {
messages
.iter()
.filter_map(|m| m.as_llm().cloned())
.collect()
}
pub(crate) fn derive_config_segment(config: &AgentLoopConfig) -> String {
if let Some(ref id) = config.config_id {
return id.clone();
}
let slugify = |s: &str| -> String {
s.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' {
c
} else {
'-'
}
})
.collect()
};
let thinking_part = if config.thinking_level != ThinkingLevel::Off {
".thinking"
} else {
""
};
format!(
"{}.{}{}",
config.model_config.provider,
slugify(&config.model_config.id),
thinking_part
)
}
pub(super) fn apply_input_filters(
mut messages: Vec<AgentMessage>,
filters: &[std::sync::Arc<dyn InputFilter>],
tx: &mpsc::UnboundedSender<AgentEvent>,
loop_id: &str,
) -> Result<Vec<AgentMessage>, String> {
if filters.is_empty() || messages.is_empty() {
return Ok(messages);
}
let user_text: String = messages
.iter()
.filter_map(|m| {
if let AgentMessage::Llm(LlmMessage {
message: Message::User { content, .. },
..
}) = m
{
Some(
content
.iter()
.filter_map(|c| {
if let Content::Text { text } = c {
Some(text.as_str())
} else {
None
}
})
.collect::<Vec<_>>()
.join("\n"),
)
} else {
None
}
})
.collect::<Vec<_>>()
.join("\n");
let mut warnings: Vec<String> = Vec::new();
for filter in filters {
match filter.filter(&user_text) {
FilterResult::Pass => {}
FilterResult::Warn(w) => warnings.push(w),
FilterResult::Reject(reason) => {
tx.send(AgentEvent::InputRejected {
loop_id: loop_id.to_string(),
reason: reason.clone(),
})
.ok();
return Err(reason);
}
}
}
if !warnings.is_empty() {
let warning_text = warnings
.iter()
.map(|w| format!("[Warning: {}]", w))
.collect::<Vec<_>>()
.join("\n");
for msg in messages.iter_mut().rev() {
if let AgentMessage::Llm(LlmMessage {
message: Message::User { content, .. },
..
}) = msg
{
content.push(Content::Text { text: warning_text });
break;
}
}
}
Ok(messages)
}