use anyhow::Result;
use async_trait::async_trait;
use serde_json::Value;
use crate::pipeline::{AgentContext, RequestProcessor, ResponseProcessor};
pub struct MemoryInjector;
#[async_trait]
impl RequestProcessor for MemoryInjector {
async fn process(&self, ctx: &mut AgentContext) -> Result<()> {
if let Some(Value::String(mem)) = ctx.metadata.get("memory_context") {
let text = format!("## Memory Context\n{}", mem);
ctx.system_prompt_parts.push(text);
}
Ok(())
}
fn name(&self) -> &str {
"memory_injector"
}
}
pub struct StrategyInjector;
#[async_trait]
impl RequestProcessor for StrategyInjector {
async fn process(&self, ctx: &mut AgentContext) -> Result<()> {
if let Some(Value::String(strat)) = ctx.metadata.get("strategy") {
let text = format!("## Strategy\n{}", strat);
ctx.system_prompt_parts.push(text);
}
Ok(())
}
fn name(&self) -> &str {
"strategy_injector"
}
}
pub struct GoalContextInjector;
#[async_trait]
impl RequestProcessor for GoalContextInjector {
async fn process(&self, ctx: &mut AgentContext) -> Result<()> {
if let Some(Value::String(goal)) = ctx.metadata.get("goal") {
let text = format!("## Current Goal\n{}", goal);
ctx.system_prompt_parts.push(text);
}
Ok(())
}
fn name(&self) -> &str {
"goal_context"
}
}
pub struct SteerInjector;
#[async_trait]
impl RequestProcessor for SteerInjector {
async fn process(&self, ctx: &mut AgentContext) -> Result<()> {
if let Some(Value::String(steer)) = ctx.metadata.get("steer") {
let text = format!("## Steering Instructions\n{}", steer);
ctx.system_prompt_parts.push(text);
}
Ok(())
}
fn name(&self) -> &str {
"steer_injector"
}
}
pub struct CostAccumulator;
#[async_trait]
impl ResponseProcessor for CostAccumulator {
async fn process(&self, ctx: &mut AgentContext, response: &mut Value) -> Result<()> {
if let Some(usage) = response.get("usage") {
let new_tokens = usage
.get("total_tokens")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let current = ctx
.metadata
.entry("total_tokens".to_string())
.or_insert(Value::Number(0.into()));
let prev = current.as_u64().unwrap_or(0);
*current = Value::Number((prev + new_tokens).into());
}
Ok(())
}
fn name(&self) -> &str {
"cost_accumulator"
}
}
pub struct FeedbackCollector;
#[async_trait]
impl ResponseProcessor for FeedbackCollector {
async fn process(&self, ctx: &mut AgentContext, response: &mut Value) -> Result<()> {
let preview = response
.get("choices")
.and_then(|c| c.get(0))
.and_then(|c| c.get("message"))
.and_then(|m| m.get("content"))
.and_then(|c| c.as_str())
.unwrap_or_else(|| response.as_str().unwrap_or(""));
let truncated: String = preview.chars().take(100).collect();
ctx.metadata.insert(
"last_response_preview".to_string(),
Value::String(truncated),
);
Ok(())
}
fn name(&self) -> &str {
"feedback_collector"
}
}
pub struct ContextOverflowGuard;
#[async_trait]
impl ResponseProcessor for ContextOverflowGuard {
async fn process(&self, ctx: &mut AgentContext, _response: &mut Value) -> Result<()> {
if ctx.messages.len() > 50 {
let head: Vec<Value> = ctx.messages.iter().take(2).cloned().collect();
let tail: Vec<Value> = ctx.messages.iter().rev().take(20).cloned().collect::<Vec<_>>().into_iter().rev().collect();
ctx.messages = head.into_iter().chain(tail).collect();
}
Ok(())
}
fn name(&self) -> &str {
"overflow_guard"
}
}