swiftide_agents/
state.rs

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