Skip to main content

tirea_contract/runtime/tool_call/
executor.rs

1use crate::runtime::activity::ActivityManager;
2use crate::runtime::behavior::AgentBehavior;
3use crate::runtime::inference::ContextMessage;
4use crate::runtime::run::RunIdentity;
5use crate::runtime::tool_call::lifecycle::SuspendedCall;
6use crate::runtime::tool_call::{CallerContext, Tool, ToolDescriptor, ToolResult};
7use crate::thread::{Message, ToolCall};
8use crate::RunPolicy;
9use async_trait::async_trait;
10use serde_json::Value;
11use std::collections::HashMap;
12use std::sync::Arc;
13use thiserror::Error;
14use tirea_state::TrackedPatch;
15use tokio_util::sync::CancellationToken;
16
17/// Result of one tool call execution.
18#[derive(Debug, Clone)]
19pub struct ToolExecution {
20    pub call: ToolCall,
21    pub result: ToolResult,
22    pub patch: Option<TrackedPatch>,
23}
24
25/// Canonical outcome for one tool call lifecycle.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum ToolCallOutcome {
29    /// Tool execution was suspended and needs external resume/decision.
30    Suspended,
31    /// Tool execution succeeded.
32    Succeeded,
33    /// Tool execution failed.
34    Failed,
35}
36
37impl ToolCallOutcome {
38    /// Derive outcome from a concrete `ToolResult`.
39    pub fn from_tool_result(result: &ToolResult) -> Self {
40        match result.status {
41            crate::runtime::tool_call::ToolStatus::Pending => Self::Suspended,
42            crate::runtime::tool_call::ToolStatus::Error => Self::Failed,
43            crate::runtime::tool_call::ToolStatus::Success
44            | crate::runtime::tool_call::ToolStatus::Warning => Self::Succeeded,
45        }
46    }
47}
48
49/// Input envelope passed to tool execution strategies.
50pub struct ToolExecutionRequest<'a> {
51    pub tools: &'a HashMap<String, Arc<dyn Tool>>,
52    pub calls: &'a [ToolCall],
53    pub state: &'a Value,
54    pub tool_descriptors: &'a [ToolDescriptor],
55    /// Agent behavior for declarative phase dispatch.
56    pub agent_behavior: Option<&'a dyn AgentBehavior>,
57    pub activity_manager: Arc<dyn ActivityManager>,
58    pub run_policy: &'a RunPolicy,
59    pub run_identity: RunIdentity,
60    pub caller_context: CallerContext,
61    pub thread_id: &'a str,
62    pub thread_messages: &'a [Arc<Message>],
63    pub state_version: u64,
64    pub cancellation_token: Option<&'a CancellationToken>,
65}
66
67/// Output item produced by tool execution strategies.
68#[derive(Debug, Clone)]
69pub struct ToolExecutionResult {
70    pub execution: ToolExecution,
71    pub outcome: ToolCallOutcome,
72    /// Suspension payload for suspended outcomes.
73    pub suspended_call: Option<SuspendedCall>,
74    /// Unified runtime messages emitted during tool execution.
75    pub messages: Vec<ContextMessage>,
76    pub pending_patches: Vec<TrackedPatch>,
77    /// Serialized state actions captured during this tool execution (intent log).
78    pub serialized_state_actions: Vec<crate::runtime::state::SerializedStateAction>,
79}
80
81/// Error returned by custom tool executors.
82#[derive(Debug, Clone, Error)]
83pub enum ToolExecutorError {
84    #[error("tool execution cancelled")]
85    Cancelled { thread_id: String },
86    #[error("tool execution failed: {message}")]
87    Failed { message: String },
88}
89
90/// Policy controlling when resume decisions are replayed into tool execution.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum DecisionReplayPolicy {
93    /// Replay each resolved suspended call as soon as its decision arrives.
94    Immediate,
95    /// Replay only when all currently suspended calls have decisions.
96    BatchAllSuspended,
97}
98
99/// Strategy abstraction for tool execution.
100#[async_trait]
101pub trait ToolExecutor: Send + Sync {
102    async fn execute(
103        &self,
104        request: ToolExecutionRequest<'_>,
105    ) -> Result<Vec<ToolExecutionResult>, ToolExecutorError>;
106
107    /// Stable strategy label for logs/debug output.
108    fn name(&self) -> &'static str;
109
110    /// Whether apply step should enforce parallel patch conflict checks.
111    fn requires_parallel_patch_conflict_check(&self) -> bool {
112        false
113    }
114
115    /// How runtime should replay resolved suspend decisions for this executor.
116    fn decision_replay_policy(&self) -> DecisionReplayPolicy {
117        DecisionReplayPolicy::Immediate
118    }
119}