tirea_contract/io/
decision.rs1pub use crate::runtime::tool_call::lifecycle::ResumeDecisionAction;
2use crate::runtime::tool_call::lifecycle::ToolCallResume;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct ToolCallDecision {
17 pub target_id: String,
19 #[serde(flatten)]
21 pub resume: ToolCallResume,
22}
23
24impl ToolCallDecision {
25 pub fn resume(target_id: impl Into<String>, result: Value, updated_at: u64) -> Self {
27 Self {
28 target_id: target_id.into(),
29 resume: ToolCallResume {
30 decision_id: String::new(),
31 action: ResumeDecisionAction::Resume,
32 result,
33 reason: None,
34 updated_at,
35 },
36 }
37 }
38
39 pub fn cancel(
41 target_id: impl Into<String>,
42 result: Value,
43 reason: Option<String>,
44 updated_at: u64,
45 ) -> Self {
46 Self {
47 target_id: target_id.into(),
48 resume: ToolCallResume {
49 decision_id: String::new(),
50 action: ResumeDecisionAction::Cancel,
51 result,
52 reason,
53 updated_at,
54 },
55 }
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn tool_call_decision_resume_constructor_sets_resume_action() {
65 let mut decision = ToolCallDecision::resume("fc_1", Value::Bool(true), 123);
66 decision.resume.decision_id = "decision_fc_1".to_string();
67 assert_eq!(decision.target_id, "fc_1");
68 assert_eq!(decision.resume.decision_id, "decision_fc_1");
69 assert!(matches!(
70 decision.resume.action,
71 ResumeDecisionAction::Resume
72 ));
73 assert_eq!(decision.resume.result, Value::Bool(true));
74 assert!(decision.resume.reason.is_none());
75 assert_eq!(decision.resume.updated_at, 123);
76 }
77
78 #[test]
79 fn tool_call_decision_cancel_constructor_sets_cancel_action() {
80 let mut decision = ToolCallDecision::cancel(
81 "fc_2",
82 serde_json::json!({
83 "approved": false,
84 "reason": "denied by user"
85 }),
86 Some("denied by user".to_string()),
87 456,
88 );
89 decision.resume.decision_id = "decision_fc_2".to_string();
90 assert!(matches!(
91 decision.resume.action,
92 ResumeDecisionAction::Cancel
93 ));
94 assert_eq!(decision.resume.reason.as_deref(), Some("denied by user"));
95 assert_eq!(decision.resume.updated_at, 456);
96 }
97
98 #[test]
99 fn tool_call_decision_serde_flatten_roundtrip() {
100 let decision = ToolCallDecision::resume("fc_1", Value::Bool(true), 42);
101 let json = serde_json::to_value(&decision).unwrap();
102
103 assert!(json.get("resume").is_none(), "resume should be flattened");
105 assert_eq!(json["target_id"], "fc_1");
106 assert_eq!(json["action"], "resume");
107 assert_eq!(json["result"], true);
108
109 let deserialized: ToolCallDecision = serde_json::from_value(json).unwrap();
111 assert_eq!(deserialized, decision);
112 }
113}