Skip to main content

af_agent_runtime/
builder.rs

1use std::collections::BTreeSet;
2use std::sync::Arc;
3
4use af_agent::{
5    ChatModel, ContextContributor, Hook, MountedPlugins, PluginError, PromptAuthority,
6    PromptSection, ToolRegistry,
7};
8
9use crate::{
10    AgentRuntime, ApproximateTokenMeter, Compactor, ModelCompactor, RuntimeLimits, TokenMeter,
11};
12
13impl AgentRuntime {
14    pub fn new(
15        model: Arc<dyn ChatModel>,
16        model_name: impl Into<String>,
17        system_prompt: impl Into<String>,
18    ) -> Self {
19        let mut prompts = af_agent::PromptRegistry::default();
20        prompts
21            .insert(PromptSection {
22                id: "product.base".into(),
23                order: 100,
24                authority: PromptAuthority::Product,
25                source: "product".into(),
26                version: env!("CARGO_PKG_VERSION").into(),
27                content: system_prompt.into(),
28            })
29            .expect("base prompt is validated by ProductRuntime");
30        Self {
31            compactor: Arc::new(ModelCompactor::new(Arc::clone(&model))),
32            model,
33            model_name: model_name.into(),
34            prompts,
35            contexts: Vec::new(),
36            tools: ToolRegistry::new(),
37            hooks: Vec::new(),
38            limits: RuntimeLimits::default(),
39            meter: Arc::new(ApproximateTokenMeter),
40            plugins: None,
41            reasoning_effort: None,
42            output_policy: None,
43        }
44    }
45
46    pub fn with_tools(mut self, tools: ToolRegistry) -> Self {
47        self.tools = tools;
48        self
49    }
50
51    pub fn with_hooks(mut self, hooks: Vec<Arc<dyn Hook>>) -> Self {
52        self.hooks = hooks;
53        self
54    }
55
56    pub fn with_limits(mut self, limits: RuntimeLimits) -> Self {
57        self.limits = limits;
58        self
59    }
60
61    pub fn with_token_meter(mut self, meter: Arc<dyn TokenMeter>) -> Self {
62        self.meter = meter;
63        self
64    }
65
66    pub fn with_compactor(mut self, compactor: Arc<dyn Compactor>) -> Self {
67        self.compactor = compactor;
68        self
69    }
70
71    pub fn with_reasoning_effort(mut self, effort: af_llm::ReasoningEffort) -> Self {
72        self.reasoning_effort = Some(effort);
73        self
74    }
75
76    pub fn with_output_policy(mut self, schema: serde_json::Value) -> Result<Self, PluginError> {
77        af_agent::validate_json_schema_definition(&schema).map_err(PluginError::Conflict)?;
78        self.output_policy = Some(schema);
79        Ok(self)
80    }
81
82    pub fn with_prompt_section(mut self, section: PromptSection) -> Result<Self, PluginError> {
83        self.prompts
84            .insert(section)
85            .map_err(PluginError::Conflict)?;
86        Ok(self)
87    }
88
89    pub fn with_context_contributor(mut self, context: Arc<dyn ContextContributor>) -> Self {
90        self.contexts.push(context);
91        self
92    }
93
94    pub fn with_plugins(mut self, mounted: MountedPlugins) -> Result<Self, PluginError> {
95        self.tools
96            .extend(mounted.tools())
97            .map_err(PluginError::Conflict)?;
98        self.hooks.extend(mounted.hooks().iter().cloned());
99        self.contexts.extend(mounted.contexts().iter().cloned());
100        for prompt in mounted.prompts() {
101            self.prompts
102                .insert(prompt.clone())
103                .map_err(PluginError::Conflict)?;
104        }
105        self.plugins = Some(mounted);
106        Ok(self)
107    }
108
109    pub fn with_allowed_tools(mut self, allowed: Option<&BTreeSet<String>>) -> Self {
110        if let Some(allowed) = allowed {
111            self.tools = self.tools.filtered(allowed.iter().map(String::as_str));
112        }
113        self
114    }
115}