use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const DEFAULT_ALLOWED_DECISIONS: &[&str] = &["approve", "edit", "reject"];
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum HitlDecision {
Approve,
Edit {
#[serde(rename = "edited_action")]
edited_action: EditedAction,
},
Reject,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct EditedAction {
pub name: String,
#[serde(alias = "args")]
pub args: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct InterruptConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_allowed_decisions", rename = "allowed_decisions")]
pub allowed_decisions: Vec<String>,
}
fn default_true() -> bool {
true
}
fn default_allowed_decisions() -> Vec<String> {
DEFAULT_ALLOWED_DECISIONS
.iter()
.map(|s| (*s).to_string())
.collect()
}
impl Default for InterruptConfig {
fn default() -> Self {
Self {
enabled: true,
allowed_decisions: default_allowed_decisions(),
}
}
}
impl InterruptConfig {
pub fn enabled() -> Self {
Self::default()
}
pub fn disabled() -> Self {
Self {
enabled: false,
allowed_decisions: default_allowed_decisions(),
}
}
pub fn with_allowed_decisions(mut self, decisions: Vec<String>) -> Self {
self.allowed_decisions = decisions;
self
}
pub fn from_value(v: &Value) -> Option<Self> {
if let Some(b) = v.as_bool() {
return Some(if b { Self::enabled() } else { Self::disabled() });
}
if let Some(obj) = v.as_object() {
let enabled = obj.get("enabled").and_then(|e| e.as_bool()).unwrap_or(true);
let allowed_decisions = obj
.get("allowed_decisions")
.and_then(|a| a.as_array())
.map(|arr| {
arr.iter()
.filter_map(|s| s.as_str().map(String::from))
.collect()
})
.unwrap_or_else(default_allowed_decisions);
return Some(Self {
enabled,
allowed_decisions,
});
}
None
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ActionRequest {
pub name: String,
pub args: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReviewConfig {
#[serde(rename = "action_name")]
pub action_name: String,
#[serde(rename = "allowed_decisions")]
pub allowed_decisions: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct InterruptPayload {
#[serde(rename = "action_requests")]
pub action_requests: Vec<ActionRequest>,
#[serde(rename = "review_configs")]
pub review_configs: Vec<ReviewConfig>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ResumePayload {
pub decisions: Vec<HitlDecision>,
}
impl ResumePayload {
pub fn from_value(v: &Value) -> Option<Self> {
let decisions = v.get("decisions")?.as_array()?;
let decisions: Vec<HitlDecision> = decisions
.iter()
.filter_map(|d| serde_json::from_value(d.clone()).ok())
.collect();
Some(Self { decisions })
}
}
#[derive(Clone, Debug)]
pub enum AgentInvokeResult {
Complete(String),
Interrupt {
interrupt_value: serde_json::Value,
},
}
impl AgentInvokeResult {
pub fn is_interrupt(&self) -> bool {
matches!(self, AgentInvokeResult::Interrupt { .. })
}
pub fn to_json(&self) -> Result<serde_json::Value, serde_json::Error> {
match self {
AgentInvokeResult::Complete(s) => Ok(serde_json::json!({ "output": s })),
AgentInvokeResult::Interrupt { interrupt_value } => {
Ok(serde_json::json!({ "__interrupt__": [ { "value": interrupt_value } ] }))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interrupt_config_default() {
let c = InterruptConfig::default();
assert!(c.enabled);
assert_eq!(c.allowed_decisions, vec!["approve", "edit", "reject"]);
}
#[test]
fn test_interrupt_config_from_value_bool() {
let c = InterruptConfig::from_value(&serde_json::json!(true)).unwrap();
assert!(c.enabled);
let c = InterruptConfig::from_value(&serde_json::json!(false)).unwrap();
assert!(!c.enabled);
}
#[test]
fn test_interrupt_config_from_value_object() {
let c = InterruptConfig::from_value(&serde_json::json!({
"allowed_decisions": ["approve", "reject"]
}))
.unwrap();
assert!(c.enabled);
assert_eq!(c.allowed_decisions, vec!["approve", "reject"]);
}
#[test]
fn test_resume_payload_from_value() {
let v = serde_json::json!({
"decisions": [
{"type": "approve"},
{"type": "reject"}
]
});
let p = ResumePayload::from_value(&v).unwrap();
assert_eq!(p.decisions.len(), 2);
assert!(matches!(p.decisions[0], HitlDecision::Approve));
assert!(matches!(p.decisions[1], HitlDecision::Reject));
}
}