1use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::JsonObject;
13use crate::error::{Error, Result};
14use crate::ids::{SubagentRunId, ToolCallId};
15
16#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
20#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
21pub struct Interrupt {
22 pub id: String,
25 pub reason: String,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub message: Option<String>,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub tool_call_id: Option<ToolCallId>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
36 #[cfg_attr(
37 feature = "schemars",
38 schemars(with = "Option<std::collections::BTreeMap<String, serde_json::Value>>")
39 )]
40 #[cfg_attr(feature = "utoipa", schema(value_type = Option<Object>))]
41 pub response_schema: Option<JsonObject>,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub expires_at: Option<String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 #[cfg_attr(
48 feature = "schemars",
49 schemars(with = "Option<std::collections::BTreeMap<String, serde_json::Value>>")
50 )]
51 #[cfg_attr(feature = "utoipa", schema(value_type = Option<Object>))]
52 pub metadata: Option<JsonObject>,
53 #[serde(
60 default,
61 deserialize_with = "crate::serde_util::reject_null",
62 skip_serializing_if = "Option::is_none"
63 )]
64 pub subagent_run_id: Option<SubagentRunId>,
65}
66
67impl Interrupt {
68 pub fn new(id: impl Into<String>, reason: impl Into<String>) -> Self {
70 Self {
71 id: id.into(),
72 reason: reason.into(),
73 ..Default::default()
74 }
75 }
76
77 #[must_use]
79 pub fn with_subagent_run_id(mut self, subagent_run_id: impl Into<SubagentRunId>) -> Self {
80 self.subagent_run_id = Some(subagent_run_id.into());
81 self
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
90#[serde(tag = "type", rename_all = "lowercase")]
91#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
92#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
93pub enum RunOutcome {
94 Success,
96 Interrupt {
98 interrupts: Vec<Interrupt>,
101 },
102}
103
104impl RunOutcome {
105 pub fn interrupt(interrupts: impl Into<Vec<Interrupt>>) -> Self {
107 Self::Interrupt {
108 interrupts: interrupts.into(),
109 }
110 }
111
112 pub const fn is_interrupt(&self) -> bool {
114 matches!(self, Self::Interrupt { .. })
115 }
116
117 pub fn interrupts(&self) -> &[Interrupt] {
119 match self {
120 Self::Success => &[],
121 Self::Interrupt { interrupts } => interrupts,
122 }
123 }
124
125 pub fn validate(&self) -> Result<()> {
132 match self {
133 Self::Interrupt { interrupts } if interrupts.is_empty() => Err(Error::Protocol(
134 "RUN_FINISHED outcome `interrupt` requires at least one interrupt".to_owned(),
135 )),
136 _ => Ok(()),
137 }
138 }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
143#[serde(rename_all = "lowercase")]
144#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
145#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
146pub enum ResumeStatus {
147 Resolved,
149 Cancelled,
151}
152
153impl ResumeStatus {
154 pub const fn as_str(&self) -> &'static str {
156 match self {
157 Self::Resolved => "resolved",
158 Self::Cancelled => "cancelled",
159 }
160 }
161}
162
163#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
167#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
168pub struct ResumeEntry {
169 pub interrupt_id: String,
171 pub status: ResumeStatus,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub payload: Option<Value>,
179 #[serde(
184 default,
185 deserialize_with = "crate::serde_util::reject_null",
186 skip_serializing_if = "Option::is_none"
187 )]
188 #[cfg_attr(
189 feature = "schemars",
190 schemars(with = "Option<std::collections::BTreeMap<String, serde_json::Value>>")
191 )]
192 #[cfg_attr(feature = "utoipa", schema(value_type = Option<Object>))]
193 pub metadata: Option<JsonObject>,
194}
195
196impl ResumeEntry {
197 pub fn resolved(interrupt_id: impl Into<String>, payload: impl Into<Value>) -> Self {
199 Self {
200 interrupt_id: interrupt_id.into(),
201 status: ResumeStatus::Resolved,
202 payload: Some(payload.into()),
203 metadata: None,
204 }
205 }
206
207 pub fn cancelled(interrupt_id: impl Into<String>) -> Self {
209 Self {
210 interrupt_id: interrupt_id.into(),
211 status: ResumeStatus::Cancelled,
212 payload: None,
213 metadata: None,
214 }
215 }
216
217 #[must_use]
219 pub fn with_metadata(mut self, metadata: JsonObject) -> Self {
220 self.metadata = Some(metadata);
221 self
222 }
223}