use super::stop_condition::StopConditionSpec;
use super::AgentDescriptor;
use crate::runtime::loop_runner::LlmRetryPolicy;
use genai::chat::ChatOptions;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolExecutionMode {
Sequential,
ParallelBatchApproval,
ParallelStreaming,
}
#[derive(Clone)]
pub struct AgentDefinition {
pub id: String,
#[allow(dead_code)]
pub name: Option<String>,
#[allow(dead_code)]
pub description: Option<String>,
pub model: String,
pub system_prompt: String,
pub max_rounds: usize,
pub tool_execution_mode: ToolExecutionMode,
pub chat_options: Option<ChatOptions>,
pub fallback_models: Vec<String>,
pub llm_retry_policy: LlmRetryPolicy,
pub behavior_ids: Vec<String>,
pub allowed_tools: Option<Vec<String>>,
pub excluded_tools: Option<Vec<String>>,
pub allowed_skills: Option<Vec<String>>,
pub excluded_skills: Option<Vec<String>>,
pub allowed_agents: Option<Vec<String>>,
pub excluded_agents: Option<Vec<String>>,
pub permission_rules: Vec<(String, String)>,
pub stop_condition_specs: Vec<StopConditionSpec>,
pub stop_condition_ids: Vec<String>,
}
impl Default for AgentDefinition {
fn default() -> Self {
Self {
id: "default".to_string(),
name: None,
description: None,
model: "gpt-4o-mini".to_string(),
system_prompt: String::new(),
max_rounds: 10,
tool_execution_mode: ToolExecutionMode::ParallelStreaming,
chat_options: Some(
ChatOptions::default()
.with_capture_usage(true)
.with_capture_reasoning_content(true)
.with_capture_tool_calls(true),
),
fallback_models: Vec::new(),
llm_retry_policy: LlmRetryPolicy::default(),
behavior_ids: Vec::new(),
allowed_tools: None,
excluded_tools: None,
allowed_skills: None,
excluded_skills: None,
allowed_agents: None,
excluded_agents: None,
permission_rules: Vec::new(),
stop_condition_specs: Vec::new(),
stop_condition_ids: Vec::new(),
}
}
}
impl std::fmt::Debug for AgentDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AgentDefinition")
.field("id", &self.id)
.field("name", &self.name)
.field("description", &self.description)
.field("model", &self.model)
.field(
"system_prompt",
&format!("[{} chars]", self.system_prompt.len()),
)
.field("max_rounds", &self.max_rounds)
.field("tool_execution_mode", &self.tool_execution_mode)
.field("chat_options", &self.chat_options)
.field("fallback_models", &self.fallback_models)
.field("llm_retry_policy", &self.llm_retry_policy)
.field("behavior_ids", &self.behavior_ids)
.field("allowed_tools", &self.allowed_tools)
.field("excluded_tools", &self.excluded_tools)
.field("allowed_skills", &self.allowed_skills)
.field("excluded_skills", &self.excluded_skills)
.field("allowed_agents", &self.allowed_agents)
.field("excluded_agents", &self.excluded_agents)
.field("stop_condition_specs", &self.stop_condition_specs)
.field("stop_condition_ids", &self.stop_condition_ids)
.finish()
}
}
impl AgentDefinition {
tirea_contract::impl_shared_agent_builder_methods!();
#[must_use]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn display_name(&self) -> &str {
self.name.as_deref().unwrap_or(&self.id)
}
#[must_use]
pub fn display_description(&self) -> &str {
self.description.as_deref().unwrap_or("")
}
#[must_use]
pub fn descriptor(&self) -> AgentDescriptor {
AgentDescriptor::new(self.id.clone())
.with_name(self.display_name())
.with_description(self.display_description())
}
#[must_use]
pub fn with_stop_condition_spec(mut self, spec: StopConditionSpec) -> Self {
self.stop_condition_specs.push(spec);
self
}
#[must_use]
pub fn with_stop_condition_specs(mut self, specs: Vec<StopConditionSpec>) -> Self {
self.stop_condition_specs = specs;
self
}
#[must_use]
pub fn with_tool_execution_mode(mut self, mode: ToolExecutionMode) -> Self {
self.tool_execution_mode = mode;
self
}
#[must_use]
pub fn with_behavior_ids(mut self, behavior_ids: Vec<String>) -> Self {
self.behavior_ids = behavior_ids;
self
}
#[must_use]
pub fn with_behavior_id(mut self, behavior_id: impl Into<String>) -> Self {
self.behavior_ids.push(behavior_id.into());
self
}
#[must_use]
pub fn with_stop_condition_id(mut self, id: impl Into<String>) -> Self {
self.stop_condition_ids.push(id.into());
self
}
#[must_use]
pub fn with_stop_condition_ids(mut self, ids: Vec<String>) -> Self {
self.stop_condition_ids = ids;
self
}
#[must_use]
pub fn with_allowed_tools(mut self, tools: Vec<String>) -> Self {
self.allowed_tools = Some(tools);
self
}
#[must_use]
pub fn with_excluded_tools(mut self, tools: Vec<String>) -> Self {
self.excluded_tools = Some(tools);
self
}
#[must_use]
pub fn with_allowed_skills(mut self, skills: Vec<String>) -> Self {
self.allowed_skills = Some(skills);
self
}
#[must_use]
pub fn with_excluded_skills(mut self, skills: Vec<String>) -> Self {
self.excluded_skills = Some(skills);
self
}
#[must_use]
pub fn with_allowed_agents(mut self, agents: Vec<String>) -> Self {
self.allowed_agents = Some(agents);
self
}
#[must_use]
pub fn with_excluded_agents(mut self, agents: Vec<String>) -> Self {
self.excluded_agents = Some(agents);
self
}
pub fn with_permission_rule(
mut self,
behavior: impl Into<String>,
pattern: impl Into<String>,
) -> Self {
self.permission_rules
.push((behavior.into(), pattern.into()));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::wiring::resolve::normalize_definition_models_for_test;
#[test]
fn normalize_definition_trims_model_and_fallback_models() {
let definition =
AgentDefinition::new(" openai::gemini-2.5-flash ").with_fallback_models(vec![
" gpt-4o-mini ".to_string(),
" ".to_string(),
" claude-3-7-sonnet ".to_string(),
]);
let normalized = normalize_definition_models_for_test(definition);
assert_eq!(normalized.model, "openai::gemini-2.5-flash");
assert_eq!(
normalized.fallback_models,
vec!["gpt-4o-mini".to_string(), "claude-3-7-sonnet".to_string()]
);
}
}