Skip to main content

tirea_contract/runtime/tool_call/
executor.rs

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