use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
pub mod error;
pub mod event;
pub mod global;
pub use error::{ErrorCategory, ErrorContext, GlobalError, GlobalResult};
pub use event::{EventBuilder, GlobalEvent};
pub use event::{execution, lifecycle, message, plugin, state};
pub use global::{GlobalMessage, MessageContent, MessageMetadata};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum AgentState {
#[default]
Created,
Initializing,
Ready,
Running,
Executing,
Paused,
Interrupted,
ShuttingDown,
Shutdown,
Failed,
Destroyed,
Error(String),
}
impl fmt::Display for AgentState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AgentState::Created => write!(f, "Created"),
AgentState::Initializing => write!(f, "Initializing"),
AgentState::Ready => write!(f, "Ready"),
AgentState::Executing => write!(f, "Executing"),
AgentState::Paused => write!(f, "Paused"),
AgentState::Interrupted => write!(f, "Interrupted"),
AgentState::ShuttingDown => write!(f, "ShuttingDown"),
AgentState::Shutdown => write!(f, "Shutdown"),
AgentState::Failed => write!(f, "Failed"),
AgentState::Error(msg) => write!(f, "Error({})", msg),
AgentState::Running => {
write!(f, "Running")
}
AgentState::Destroyed => {
write!(f, "Destroyed")
}
}
}
}
impl AgentState {
pub fn transition_to(
&self,
target: AgentState,
) -> Result<AgentState, super::error::AgentError> {
if self.can_transition_to(&target) {
Ok(target)
} else {
Err(super::error::AgentError::invalid_state_transition(
self, &target,
))
}
}
pub fn can_transition_to(&self, target: &AgentState) -> bool {
use AgentState::*;
matches!(
(self, target),
(Created, Initializing)
| (Initializing, Ready)
| (Initializing, Error(_))
| (Initializing, Failed)
| (Ready, Executing)
| (Ready, ShuttingDown)
| (Executing, Ready)
| (Executing, Paused)
| (Executing, Interrupted)
| (Executing, Error(_))
| (Executing, Failed)
| (Paused, Ready)
| (Paused, Executing)
| (Paused, ShuttingDown)
| (Interrupted, Ready)
| (Interrupted, ShuttingDown)
| (ShuttingDown, Shutdown)
| (Error(_), ShuttingDown)
| (Error(_), Shutdown)
| (Failed, ShuttingDown)
| (Failed, Shutdown)
)
}
pub fn is_active(&self) -> bool {
matches!(self, AgentState::Ready | AgentState::Executing)
}
pub fn is_terminal(&self) -> bool {
matches!(
self,
AgentState::Shutdown | AgentState::Failed | AgentState::Error(_)
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum AgentInput {
Text(String),
Texts(Vec<String>),
Json(serde_json::Value),
Map(HashMap<String, serde_json::Value>),
Binary(Vec<u8>),
#[default]
Empty,
}
impl AgentInput {
pub fn text(s: impl Into<String>) -> Self {
Self::Text(s.into())
}
pub fn json(value: serde_json::Value) -> Self {
Self::Json(value)
}
pub fn map(map: HashMap<String, serde_json::Value>) -> Self {
Self::Map(map)
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(s) => Some(s),
_ => None,
}
}
pub fn to_text(&self) -> String {
match self {
Self::Text(s) => s.clone(),
Self::Texts(v) => v.join("\n"),
Self::Json(v) => v.to_string(),
Self::Map(m) => serde_json::to_string(m).unwrap_or_default(),
Self::Binary(b) => String::from_utf8_lossy(b).to_string(),
Self::Empty => String::new(),
}
}
pub fn as_json(&self) -> Option<&serde_json::Value> {
match self {
Self::Json(v) => Some(v),
_ => None,
}
}
pub fn to_json(&self) -> serde_json::Value {
match self {
Self::Text(s) => serde_json::Value::String(s.clone()),
Self::Texts(v) => serde_json::json!(v),
Self::Json(v) => v.clone(),
Self::Map(m) => serde_json::to_value(m).unwrap_or_default(),
Self::Binary(b) => serde_json::json!({ "binary": base64_encode(b) }),
Self::Empty => serde_json::Value::Null,
}
}
pub fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
}
impl From<String> for AgentInput {
fn from(s: String) -> Self {
Self::Text(s)
}
}
impl From<&str> for AgentInput {
fn from(s: &str) -> Self {
Self::Text(s.to_string())
}
}
impl From<serde_json::Value> for AgentInput {
fn from(v: serde_json::Value) -> Self {
Self::Json(v)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentOutput {
pub content: OutputContent,
pub metadata: HashMap<String, serde_json::Value>,
pub tools_used: Vec<ToolUsage>,
pub reasoning_steps: Vec<ReasoningStep>,
pub duration_ms: u64,
pub token_usage: Option<TokenUsage>,
}
impl Default for AgentOutput {
fn default() -> Self {
Self {
content: OutputContent::Empty,
metadata: HashMap::new(),
tools_used: Vec::new(),
reasoning_steps: Vec::new(),
duration_ms: 0,
token_usage: None,
}
}
}
impl AgentOutput {
pub fn text(s: impl Into<String>) -> Self {
Self {
content: OutputContent::Text(s.into()),
..Default::default()
}
}
pub fn json(value: serde_json::Value) -> Self {
Self {
content: OutputContent::Json(value),
..Default::default()
}
}
pub fn error(message: impl Into<String>) -> Self {
Self {
content: OutputContent::Error(message.into()),
..Default::default()
}
}
pub fn as_text(&self) -> Option<&str> {
match &self.content {
OutputContent::Text(s) => Some(s),
_ => None,
}
}
pub fn to_text(&self) -> String {
self.content.to_text()
}
pub fn with_duration(mut self, duration_ms: u64) -> Self {
self.duration_ms = duration_ms;
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.metadata.insert(key.into(), value);
self
}
pub fn with_tool_usage(mut self, usage: ToolUsage) -> Self {
self.tools_used.push(usage);
self
}
pub fn with_tools_used(mut self, usages: Vec<ToolUsage>) -> Self {
self.tools_used = usages;
self
}
pub fn with_reasoning_step(mut self, step: ReasoningStep) -> Self {
self.reasoning_steps.push(step);
self
}
pub fn with_reasoning_steps(mut self, steps: Vec<ReasoningStep>) -> Self {
self.reasoning_steps = steps;
self
}
pub fn with_token_usage(mut self, usage: TokenUsage) -> Self {
self.token_usage = Some(usage);
self
}
pub fn is_error(&self) -> bool {
matches!(self.content, OutputContent::Error(_))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OutputContent {
Text(String),
Texts(Vec<String>),
Json(serde_json::Value),
Binary(Vec<u8>),
Stream,
Error(String),
Empty,
}
impl OutputContent {
pub fn to_text(&self) -> String {
match self {
Self::Text(s) => s.clone(),
Self::Texts(v) => v.join("\n"),
Self::Json(v) => v.to_string(),
Self::Binary(b) => String::from_utf8_lossy(b).to_string(),
Self::Stream => "[STREAM]".to_string(),
Self::Error(e) => format!("Error: {}", e),
Self::Empty => String::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolUsage {
pub name: String,
pub input: serde_json::Value,
pub output: Option<serde_json::Value>,
pub success: bool,
pub error: Option<String>,
pub duration_ms: u64,
}
impl ToolUsage {
pub fn success(
name: impl Into<String>,
input: serde_json::Value,
output: serde_json::Value,
duration_ms: u64,
) -> Self {
Self {
name: name.into(),
input,
output: Some(output),
success: true,
error: None,
duration_ms,
}
}
pub fn failure(
name: impl Into<String>,
input: serde_json::Value,
error: impl Into<String>,
duration_ms: u64,
) -> Self {
Self {
name: name.into(),
input,
output: None,
success: false,
error: Some(error.into()),
duration_ms,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReasoningStep {
pub step_type: ReasoningStepType,
pub content: String,
pub step_number: usize,
pub timestamp_ms: u64,
}
impl ReasoningStep {
pub fn new(
step_type: ReasoningStepType,
content: impl Into<String>,
step_number: usize,
) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
Self {
step_type,
content: content.into(),
step_number,
timestamp_ms: now,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReasoningStepType {
Thought,
Action,
Observation,
Reflection,
Decision,
FinalAnswer,
Custom(String),
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenUsage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
impl TokenUsage {
pub fn new(prompt_tokens: u32, completion_tokens: u32) -> Self {
let total_tokens = prompt_tokens + completion_tokens;
Self {
prompt_tokens,
completion_tokens,
total_tokens,
}
}
}
#[derive(Debug, Clone)]
pub struct ChatCompletionRequest {
pub messages: Vec<ChatMessage>,
pub model: Option<String>,
pub tools: Option<Vec<ToolDefinition>>,
pub temperature: Option<f32>,
pub max_tokens: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: String,
pub content: Option<String>,
pub tool_call_id: Option<String>,
pub tool_calls: Option<Vec<ToolCall>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct ChatCompletionResponse {
pub content: Option<String>,
pub tool_calls: Option<Vec<ToolCall>>,
pub usage: Option<TokenUsage>,
}
#[async_trait]
pub trait LLMProvider: Send + Sync {
fn name(&self) -> &str;
async fn chat(
&self,
request: ChatCompletionRequest,
) -> super::error::AgentResult<ChatCompletionResponse>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InterruptResult {
Acknowledged,
Paused,
Interrupted {
partial_result: Option<String>,
},
TaskTerminated {
partial_result: Option<AgentOutput>,
},
Ignored,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum InputType {
Text,
Image,
Audio,
Video,
Structured(String),
Binary,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OutputType {
Text,
Json,
StructuredJson,
Stream,
Binary,
Multimodal,
}
fn base64_encode(data: &[u8]) -> String {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = Vec::new();
for chunk in data.chunks(3) {
let (n, _pad) = match chunk.len() {
1 => (((chunk[0] as u32) << 16), 2),
2 => (((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8), 1),
_ => (
((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8) | (chunk[2] as u32),
0,
),
};
result.push(CHARS[((n >> 18) & 0x3F) as usize]);
result.push(CHARS[((n >> 12) & 0x3F) as usize]);
if chunk.len() > 1 {
result.push(CHARS[((n >> 6) & 0x3F) as usize]);
} else {
result.push(b'=');
}
if chunk.len() > 2 {
result.push(CHARS[(n & 0x3F) as usize]);
} else {
result.push(b'=');
}
}
String::from_utf8(result).unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_state_transitions() {
let state = AgentState::Created;
assert!(state.can_transition_to(&AgentState::Initializing));
assert!(!state.can_transition_to(&AgentState::Executing));
}
#[test]
fn test_agent_input_text() {
let input = AgentInput::text("Hello");
assert_eq!(input.as_text(), Some("Hello"));
assert_eq!(input.to_text(), "Hello");
}
#[test]
fn test_agent_output_text() {
let output = AgentOutput::text("World")
.with_duration(100)
.with_metadata("key", serde_json::json!("value"));
assert_eq!(output.as_text(), Some("World"));
assert_eq!(output.duration_ms, 100);
assert!(output.metadata.contains_key("key"));
}
#[test]
fn test_tool_usage() {
let usage = ToolUsage::success(
"calculator",
serde_json::json!({"a": 1, "b": 2}),
serde_json::json!(3),
50,
);
assert!(usage.success);
assert_eq!(usage.name, "calculator");
}
}