use std::sync::Arc;
use crate::config::SessionConfig;
use crate::contributor::{ContextContributor, ContributorContext};
use crate::engine::BareLoop;
use crate::engine::RunConfig;
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(OutputLimitMiddleware::new(OUTPUT_CAP_CHARS))
.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,
))
}
#[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> {
let manager = crate::compact::ContextManager::new(Arc::new(
crate::compact::TruncatingCompactor::default(),
))
.with_context_window(loop_.session_config().context_window)
.with_threshold(loop_.session_config().compact_threshold);
loop_.set_context_manager(Arc::new(manager));
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:?}"),
}
}
#[tokio::test]
async fn constrained_profile_output_cap_bounds_final_tool_output() {
use crate::message::{ToolContent, ToolContentPart};
use crate::middleware::ToolDispatchContext;
use crate::tool::{
PermissionCheck, Tool, ToolContext, ToolOutput, ToolRegistry, ToolSchema,
};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
struct CapSizedWriteTool;
impl Tool for CapSizedWriteTool {
fn name(&self) -> &'static str {
"Write"
}
fn description(&self) -> &'static str {
"Returns exactly the profile cap in characters"
}
fn schema(&self) -> ToolSchema {
ToolSchema {
tool: self.name().to_string(),
description: self.description().to_string(),
input_schema: serde_json::json!({"type": "object", "properties": {}}),
}
}
fn call(
&self,
_input: serde_json::Value,
_ctx: &ToolContext,
) -> Pin<Box<dyn Future<Output = Result<ToolOutput, crate::tool::ToolError>> + Send + '_>>
{
Box::pin(async move {
Ok(ToolOutput {
payload: ToolContent::Text("w".repeat(OUTPUT_CAP_CHARS)),
is_error: false,
display_hint: None,
})
})
}
}
let mut registry = ToolRegistry::new();
registry.register(CapSizedWriteTool);
let pipeline = ConstrainedProfile::pipeline_builder()
.with_core(Arc::new(registry))
.build()
.expect("pipeline builds");
let ctx = ToolDispatchContext {
tool_name: "Write".to_string(),
input: serde_json::json!({}),
call_id: "c1".to_string(),
turn_number: 0,
cancel: Arc::new(crate::cancel::CancelSignal::new()),
permission: PermissionCheck::Allow,
tool_context: ToolContext::default(),
};
let result = pipeline.invoke(ctx).await;
let len = match &result.output {
ToolContent::Text(text) => text.chars().count(),
ToolContent::Multipart(parts) => parts
.iter()
.map(|p| match p {
ToolContentPart::Text { text } => text.chars().count(),
ToolContentPart::Image { .. } => 0,
})
.sum(),
};
let expected = OUTPUT_CAP_CHARS;
assert_eq!(
len, expected,
"doc: verify's appended diagnostics flow through the output cap — the combined \
output must truncate to exactly the cap — the marker is inside the budget"
);
}
}