1use serde::{Deserialize, Serialize};
4
5#[derive(
8 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
9)]
10#[serde(rename_all = "kebab-case")]
11pub enum RiskTier {
12 Read,
13 Write,
14 NetworkEgress,
15 Spend,
16 Destructive,
17 Privileged,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum HitlMode {
24 Auto,
26 Ask,
28 Deny,
30}
31
32pub fn default_mode(tier: RiskTier) -> HitlMode {
35 match tier {
36 RiskTier::Read => HitlMode::Auto,
37 _ => HitlMode::Ask,
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct HitlRequest {
44 pub hitl_id: String,
45 pub action_hash: String,
47 pub tier: RiskTier,
48 pub tool_name: String,
49 pub tool_input: serde_json::Value,
50 pub step_or_call_id: String,
51 pub agent_id: String,
52 pub timeout_ms: u64,
53 pub summary: String,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct HitlResponse {
59 pub hitl_id: String,
60 pub action_hash: String,
61 pub allow: bool,
62 #[serde(default)]
63 pub reason: String,
64 pub surface: String,
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn tier_orders_by_severity_and_maps_mode() {
74 assert!(RiskTier::Read < RiskTier::Destructive);
75 assert!(RiskTier::Write < RiskTier::Privileged);
76 assert_eq!(default_mode(RiskTier::Read), HitlMode::Auto);
77 assert_eq!(default_mode(RiskTier::Destructive), HitlMode::Ask);
78 }
79
80 #[test]
81 fn hitl_payloads_round_trip() {
82 let req = HitlRequest {
83 hitl_id: "h1".into(),
84 action_hash: "abc".into(),
85 tier: RiskTier::Destructive,
86 tool_name: "bash".into(),
87 tool_input: serde_json::json!({ "cmd": "rm -rf x" }),
88 step_or_call_id: "s0".into(),
89 agent_id: "mur".into(),
90 timeout_ms: 300_000,
91 summary: "delete x".into(),
92 };
93 let s = serde_json::to_string(&req).unwrap();
94 let back: HitlRequest = serde_json::from_str(&s).unwrap();
95 assert_eq!(back.tier, RiskTier::Destructive);
96 assert_eq!(back.action_hash, "abc");
97 }
98}