swiftide_agents/
state.rs

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