agents_core/
agent.rs

1use async_trait::async_trait;
2use futures::stream::Stream;
3use serde::{Deserialize, Serialize};
4use std::pin::Pin;
5use std::sync::Arc;
6
7use crate::llm::StreamChunk;
8use crate::messaging::AgentMessage;
9use crate::state::AgentStateSnapshot;
10
11/// Planner interface responsible for deciding which actions to take.
12#[async_trait]
13pub trait PlannerHandle: Send + Sync + std::any::Any {
14    async fn plan(
15        &self,
16        context: PlannerContext,
17        state: Arc<AgentStateSnapshot>,
18    ) -> anyhow::Result<PlannerDecision>;
19
20    /// Enable downcasting to concrete types
21    fn as_any(&self) -> &dyn std::any::Any;
22}
23
24/// Minimal metadata about an agent instance.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct AgentDescriptor {
27    pub name: String,
28    pub version: String,
29    pub description: Option<String>,
30}
31
32/// Message that returns the planner's decision for the next step.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct PlannerDecision {
35    pub next_action: PlannerAction,
36}
37
38/// High-level actions a planner can request from the runtime.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub enum PlannerAction {
41    CallTool {
42        tool_name: String,
43        payload: serde_json::Value,
44    },
45    Respond {
46        message: AgentMessage,
47    },
48    Terminate,
49}
50
51/// Context passed to planners containing the latest exchange history.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct PlannerContext {
54    pub history: Vec<AgentMessage>,
55    pub system_prompt: String,
56    #[serde(default)]
57    pub tools: Vec<crate::tools::ToolSchema>,
58}
59
60/// Type alias for a stream of agent response chunks
61pub type AgentStream = Pin<Box<dyn Stream<Item = anyhow::Result<StreamChunk>> + Send>>;
62
63/// Abstraction for hosting a fully configured agent (planner + tools + prompts).
64#[async_trait]
65pub trait AgentHandle: Send + Sync {
66    async fn describe(&self) -> AgentDescriptor;
67
68    async fn handle_message(
69        &self,
70        input: AgentMessage,
71        state: Arc<AgentStateSnapshot>,
72    ) -> anyhow::Result<AgentMessage>;
73
74    /// Handle a message with streaming response
75    /// Default implementation falls back to non-streaming handle_message()
76    async fn handle_message_stream(
77        &self,
78        input: AgentMessage,
79        state: Arc<AgentStateSnapshot>,
80    ) -> anyhow::Result<AgentStream> {
81        // Default: call non-streaming and wrap result
82        let response = self.handle_message(input, state).await?;
83        Ok(Box::pin(futures::stream::once(async move {
84            Ok(StreamChunk::Done { message: response })
85        })))
86    }
87
88    /// Get the current pending interrupt if any
89    /// Returns None if no interrupts are pending
90    async fn current_interrupt(&self) -> anyhow::Result<Option<crate::hitl::AgentInterrupt>> {
91        // Default implementation returns None
92        Ok(None)
93    }
94
95    /// Resume execution after human approval of an interrupt
96    ///
97    /// # Arguments
98    /// * `action` - The human's decision (Accept, Edit, Reject, or Respond)
99    ///
100    /// # Returns
101    /// The agent's response after processing the action
102    async fn resume_with_approval(
103        &self,
104        _action: crate::hitl::HitlAction,
105    ) -> anyhow::Result<AgentMessage> {
106        // Default implementation returns an error
107        anyhow::bail!("resume_with_approval not implemented for this agent")
108    }
109}
110
111// ToolResponse has been removed - use ToolResult from crate::tools instead