swiftide_agents/
state.rs

1//! Internal state of the agent
2
3use std::borrow::Cow;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use swiftide_core::chat_completion::ToolCall;
8
9#[derive(Clone, Debug, Default, strum_macros::EnumDiscriminants, strum_macros::EnumIs)]
10pub enum State {
11    #[default]
12    Pending,
13    Running,
14    Stopped(StopReason),
15}
16
17impl State {
18    pub fn stop_reason(&self) -> Option<&StopReason> {
19        match self {
20            State::Stopped(reason) => Some(reason),
21            _ => None,
22        }
23    }
24}
25
26/// The reason the agent stopped
27///
28/// `StopReason::Other` has some convenience methods to convert from any `AsRef<str>`
29#[non_exhaustive]
30#[derive(Clone, Debug, strum_macros::EnumIs, PartialEq, Serialize, Deserialize)]
31pub enum StopReason {
32    /// A tool called stop
33    RequestedByTool(ToolCall, Option<Value>),
34
35    /// Agent failed to complete with optional message
36    AgentFailed(Option<Cow<'static, str>>),
37
38    /// A tool repeatedly failed
39    ToolCallsOverLimit(ToolCall),
40
41    /// A tool requires feedback before it will continue
42    FeedbackRequired {
43        tool_call: ToolCall,
44        payload: Option<serde_json::Value>,
45    },
46    /// There was an error
47    Error,
48
49    /// No new messages; stopping completions
50    NoNewMessages,
51
52    Other(String),
53}
54
55impl StopReason {
56    pub fn as_requested_by_tool(&self) -> Option<(&ToolCall, Option<&Value>)> {
57        if let StopReason::RequestedByTool(t, message) = self {
58            Some((t, message.as_ref()))
59        } else {
60            None
61        }
62    }
63
64    pub fn as_tool_calls_over_limit(&self) -> Option<&ToolCall> {
65        if let StopReason::ToolCallsOverLimit(t) = self {
66            Some(t)
67        } else {
68            None
69        }
70    }
71
72    pub fn as_feedback_required(&self) -> Option<(&ToolCall, Option<&serde_json::Value>)> {
73        if let StopReason::FeedbackRequired { tool_call, payload } = self {
74            Some((tool_call, payload.as_ref()))
75        } else {
76            None
77        }
78    }
79
80    pub fn as_error(&self) -> Option<()> {
81        if matches!(self, StopReason::Error) {
82            Some(())
83        } else {
84            None
85        }
86    }
87
88    pub fn as_no_new_messages(&self) -> Option<()> {
89        if matches!(self, StopReason::NoNewMessages) {
90            Some(())
91        } else {
92            None
93        }
94    }
95
96    pub fn as_other(&self) -> Option<&str> {
97        if let StopReason::Other(s) = self {
98            Some(s)
99        } else {
100            None
101        }
102    }
103}
104impl Default for StopReason {
105    fn default() -> Self {
106        StopReason::Other("No reason provided".into())
107    }
108}
109
110impl<S: AsRef<str>> From<S> for StopReason {
111    fn from(value: S) -> Self {
112        StopReason::Other(value.as_ref().to_string())
113    }
114}