use std::sync::Arc;
use crate::config::SessionConfig;
use crate::engine::RunConfig;
use crate::engine::{BareLoop, ContextContributor, ContributorContext};
use crate::error::LoopError;
use crate::message::{Message, MessagePart, Role};
use crate::middleware::{
MemoizingMiddleware, NoopPathExtractor, NoopVerifier, OutputLimitMiddleware, ToolPipeline,
VerifyMiddleware,
};
use crate::structured::{RequestOptions, ToolConstraint};
const OUTPUT_CAP_CHARS: usize = 16_384;
const MEMOIZE_TTL_TURNS: u32 = 5;
const GOAL_REMINDER_EVERY_N_TURNS: usize = 5;
const WRITE_TOOLS: &[&str] = &["Write", "Edit", "MultiEdit"];
const MEMOIZED_TOOLS: &[&str] = &["Read", "Glob", "Grep", "LS"];
pub struct ConstrainedProfile;
impl ConstrainedProfile {
#[must_use]
pub fn session_config() -> SessionConfig {
SessionConfig::default().with_context_window(32_768)
}
#[must_use]
pub fn run_config() -> RunConfig {
RunConfig {
max_turns: 100,
..RunConfig::default()
}
}
#[must_use]
pub fn pipeline_builder() -> crate::middleware::ToolPipelineBuilder {
ToolPipeline::builder()
.with_middleware(VerifyMiddleware::new(
Arc::new(NoopVerifier),
WRITE_TOOLS.iter().map(|s| (*s).to_string()).collect(),
))
.with_middleware(MemoizingMiddleware::new(
MEMOIZED_TOOLS.iter().map(|s| (*s).to_string()).collect(),
WRITE_TOOLS.iter().map(|s| (*s).to_string()).collect(),
Arc::new(NoopPathExtractor),
MEMOIZE_TTL_TURNS,
))
.with_middleware(OutputLimitMiddleware::new(OUTPUT_CAP_CHARS))
}
#[must_use]
pub fn request_options() -> RequestOptions {
RequestOptions::new().with_tool_constraint(ToolConstraint::Strict)
}
pub fn apply<C: crate::api::ApiClient>(loop_: &mut BareLoop<C>) -> Result<(), LoopError> {
loop_.set_pipeline(Self::pipeline_builder())?;
loop_.add_contributor(Box::new(GoalReminder::new(GOAL_REMINDER_EVERY_N_TURNS)));
Ok(())
}
}
pub struct FrontierProfile;
impl FrontierProfile {
#[must_use]
pub fn session_config() -> SessionConfig {
SessionConfig::default()
}
#[must_use]
pub fn run_config() -> RunConfig {
RunConfig::default()
}
#[must_use]
pub fn pipeline_builder() -> crate::middleware::ToolPipelineBuilder {
ToolPipeline::builder()
}
#[must_use]
pub fn request_options() -> RequestOptions {
RequestOptions::default()
}
}
pub struct GoalReminder {
every_n_turns: usize,
}
impl GoalReminder {
#[must_use]
pub fn new(every_n_turns: usize) -> Self {
Self { every_n_turns }
}
}
impl ContextContributor for GoalReminder {
fn contribute(&self, ctx: &ContributorContext<'_>) -> Option<Message> {
if ctx.turn == 0 || self.every_n_turns == 0 {
return None;
}
if !ctx.turn.is_multiple_of(self.every_n_turns) {
return None;
}
let first_user_text = ctx.conversation.iter().find_map(|m| {
if !matches!(m.role, Role::User) {
return None;
}
let texts: Vec<&str> = m
.parts
.iter()
.filter_map(|p| match p {
MessagePart::Text { text } => Some(text.as_str()),
_ => None,
})
.collect();
if texts.is_empty() {
None
} else {
Some(texts.join("\n"))
}
})?;
Some(Message::new(
Role::System,
vec![MessagePart::text(first_user_text)],
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constrained_config_values() {
let session = ConstrainedProfile::session_config();
assert_eq!(session.context_window, 32_768);
let run = ConstrainedProfile::run_config();
assert_eq!(run.max_turns, 100);
}
#[test]
fn test_constrained_request_options_strict() {
let opts = ConstrainedProfile::request_options();
assert!(matches!(opts.tool_constraint, ToolConstraint::Strict));
assert!(opts.response_format.is_none());
}
#[test]
fn test_frontier_config_matches_default() {
let session = FrontierProfile::session_config();
assert_eq!(
session.context_window,
SessionConfig::default().context_window
);
let run = FrontierProfile::run_config();
let default = RunConfig::default();
assert_eq!(run.max_turns, default.max_turns);
}
#[test]
fn test_frontier_request_options_default() {
let opts = FrontierProfile::request_options();
assert!(matches!(opts.tool_constraint, ToolConstraint::None));
assert!(opts.response_format.is_none());
}
fn ctx_at(turn: usize, conv: &[Message]) -> ContributorContext<'_> {
ContributorContext::new(turn, conv)
}
#[test]
fn test_goal_reminder_skips_turn_zero() {
let reminder = GoalReminder::new(5);
let conv = [Message::user("ship the demo")];
let ctx = ctx_at(0, &conv);
assert!(
reminder.contribute(&ctx).is_none(),
"turn 0 must be skipped"
);
}
#[test]
fn test_goal_reminder_never_fires_when_n_is_zero() {
let reminder = GoalReminder::new(0);
let conv = [Message::user("ship the demo")];
let ctx = ctx_at(5, &conv);
assert!(
reminder.contribute(&ctx).is_none(),
"n=0 disables the reminder"
);
}
#[test]
fn test_goal_reminder_fires_every_n_turns() {
let reminder = GoalReminder::new(5);
let conv = [Message::user("goal")];
for turn in [1, 2, 3, 4, 6, 7, 8, 9, 11] {
let ctx = ctx_at(turn, &conv);
assert!(
reminder.contribute(&ctx).is_none(),
"turn {turn} must not fire (cadence 5)"
);
}
for turn in [5, 10, 15, 20] {
let ctx = ctx_at(turn, &conv);
assert!(
reminder.contribute(&ctx).is_some(),
"turn {turn} must fire (cadence 5)"
);
}
}
#[test]
fn test_goal_reminder_injects_first_user_message_verbatim() {
let reminder = GoalReminder::new(5);
let conv = [
Message::assistant("hi"), Message::user("ship the demo"), Message::user("a later unrelated request"), ];
let ctx = ctx_at(5, &conv);
let msg = reminder.contribute(&ctx).expect("turn 5 fires");
assert_eq!(msg.role, Role::System);
match &msg.parts[0] {
MessagePart::Text { text } => assert_eq!(text, "ship the demo"),
other => panic!("expected Text part, got {other:?}"),
}
}
#[test]
fn test_goal_reminder_returns_none_with_no_user_message() {
let reminder = GoalReminder::new(5);
let conv = [Message::assistant("no user here")];
let ctx = ctx_at(5, &conv);
assert!(
reminder.contribute(&ctx).is_none(),
"no user message → no reminder, even on a firing turn"
);
}
#[test]
fn test_goal_reminder_handles_multi_part_first_user_message() {
let reminder = GoalReminder::new(1);
let conv = [Message::new(
Role::User,
vec![MessagePart::text("line one"), MessagePart::text("line two")],
)];
let ctx = ctx_at(1, &conv);
let msg = reminder.contribute(&ctx).expect("turn 1 fires (cadence 1)");
match &msg.parts[0] {
MessagePart::Text { text } => {
assert_eq!(text, "line one\nline two");
}
other => panic!("expected Text part, got {other:?}"),
}
}
}