pub struct FragmentContext<'a> {
pub tool_definitions: &'a [serde_json::Value],
pub session_id: &'a str,
}
pub trait PromptFragment: Send + Sync + dyn_clone::DynClone {
fn name(&self) -> &str;
fn priority(&self) -> i32 {
100
}
fn render(&self, ctx: &FragmentContext) -> Option<String>;
}
dyn_clone::clone_trait_object!(PromptFragment);
pub fn compose_fragments(fragments: &[Box<dyn PromptFragment>], ctx: &FragmentContext) -> String {
let mut sorted: Vec<&Box<dyn PromptFragment>> = fragments.iter().collect();
sorted.sort_by_key(|f| f.priority());
sorted
.iter()
.filter_map(|f| f.render(ctx))
.collect::<Vec<_>>()
.join("\n\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct AlphaFragment;
impl PromptFragment for AlphaFragment {
fn name(&self) -> &str {
"alpha"
}
fn priority(&self) -> i32 {
20
}
fn render(&self, _ctx: &FragmentContext) -> Option<String> {
Some("ALPHA".to_string())
}
}
#[derive(Clone)]
struct BetaFragment;
impl PromptFragment for BetaFragment {
fn name(&self) -> &str {
"beta"
}
fn priority(&self) -> i32 {
10
}
fn render(&self, _ctx: &FragmentContext) -> Option<String> {
Some("BETA".to_string())
}
}
#[derive(Clone)]
struct SkipFragment;
impl PromptFragment for SkipFragment {
fn name(&self) -> &str {
"skip"
}
fn render(&self, _ctx: &FragmentContext) -> Option<String> {
None
}
}
#[test]
fn compose_sorts_by_priority() {
let fragments: Vec<Box<dyn PromptFragment>> = vec![
Box::new(AlphaFragment), Box::new(BetaFragment), ];
let ctx = FragmentContext {
tool_definitions: &[],
session_id: "test",
};
let result = compose_fragments(&fragments, &ctx);
assert_eq!(result, "BETA\n\nALPHA");
}
#[test]
fn compose_skips_none() {
let fragments: Vec<Box<dyn PromptFragment>> = vec![
Box::new(AlphaFragment),
Box::new(SkipFragment),
Box::new(BetaFragment),
];
let ctx = FragmentContext {
tool_definitions: &[],
session_id: "test",
};
let result = compose_fragments(&fragments, &ctx);
assert_eq!(result, "BETA\n\nALPHA");
}
#[test]
fn compose_empty_fragments() {
let fragments: Vec<Box<dyn PromptFragment>> = vec![];
let ctx = FragmentContext {
tool_definitions: &[],
session_id: "test",
};
let result = compose_fragments(&fragments, &ctx);
assert!(result.is_empty());
}
#[test]
fn compose_all_none_returns_empty() {
let fragments: Vec<Box<dyn PromptFragment>> =
vec![Box::new(SkipFragment), Box::new(SkipFragment)];
let ctx = FragmentContext {
tool_definitions: &[],
session_id: "test",
};
let result = compose_fragments(&fragments, &ctx);
assert!(result.is_empty());
}
#[test]
fn default_priority_is_100() {
#[derive(Clone)]
struct DefaultPriorityFragment;
impl PromptFragment for DefaultPriorityFragment {
fn name(&self) -> &str {
"default"
}
fn render(&self, _ctx: &FragmentContext) -> Option<String> {
Some("default".to_string())
}
}
assert_eq!(DefaultPriorityFragment.priority(), 100);
}
}