arcane_engine/agent/
mod.rs1pub mod inspector;
2pub mod mcp;
3
4use std::sync::mpsc;
5
6#[derive(Debug)]
8pub enum InspectorRequest {
9 Health,
10 GetState { path: Option<String> },
11 Describe { verbosity: Option<String> },
12 ListActions,
13 ExecuteAction { name: String, payload: String },
14 Rewind { steps: u32 },
15 Simulate { action: String },
16 GetHistory,
17}
18
19#[derive(Debug)]
21pub struct InspectorResponse {
22 pub status: u16,
23 pub content_type: String,
24 pub body: String,
25}
26
27impl InspectorResponse {
28 pub fn json(body: String) -> Self {
29 Self {
30 status: 200,
31 content_type: "application/json".into(),
32 body,
33 }
34 }
35
36 pub fn text(body: String) -> Self {
37 Self {
38 status: 200,
39 content_type: "text/plain".into(),
40 body,
41 }
42 }
43
44 pub fn error(status: u16, message: String) -> Self {
45 Self {
46 status,
47 content_type: "application/json".into(),
48 body: format!("{{\"error\":\"{message}\"}}"),
49 }
50 }
51}
52
53pub type ResponseSender = mpsc::Sender<InspectorResponse>;
55
56pub type InspectorMessage = (InspectorRequest, ResponseSender);
58
59pub type RequestSender = mpsc::Sender<InspectorMessage>;
61
62pub type RequestReceiver = mpsc::Receiver<InspectorMessage>;
64
65pub fn inspector_channel() -> (RequestSender, RequestReceiver) {
67 mpsc::channel()
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn inspector_channel_round_trip() {
76 let (tx, rx) = inspector_channel();
77
78 let (resp_tx, resp_rx) = mpsc::channel();
80 tx.send((InspectorRequest::Health, resp_tx)).unwrap();
81
82 let (req, sender) = rx.recv().unwrap();
84 assert!(matches!(req, InspectorRequest::Health));
85 sender
86 .send(InspectorResponse::json("{\"status\":\"ok\"}".into()))
87 .unwrap();
88
89 let response = resp_rx.recv().unwrap();
91 assert_eq!(response.status, 200);
92 assert_eq!(response.content_type, "application/json");
93 assert!(response.body.contains("ok"));
94 }
95
96 #[test]
97 fn inspector_response_constructors() {
98 let json = InspectorResponse::json("{\"key\":1}".into());
99 assert_eq!(json.status, 200);
100 assert_eq!(json.content_type, "application/json");
101
102 let text = InspectorResponse::text("hello".into());
103 assert_eq!(text.status, 200);
104 assert_eq!(text.content_type, "text/plain");
105
106 let err = InspectorResponse::error(404, "not found".into());
107 assert_eq!(err.status, 404);
108 assert!(err.body.contains("not found"));
109 }
110
111 #[test]
112 fn inspector_request_variants() {
113 let requests = vec![
115 InspectorRequest::Health,
116 InspectorRequest::GetState { path: None },
117 InspectorRequest::GetState {
118 path: Some("player.hp".into()),
119 },
120 InspectorRequest::Describe {
121 verbosity: Some("full".into()),
122 },
123 InspectorRequest::ListActions,
124 InspectorRequest::ExecuteAction {
125 name: "move".into(),
126 payload: "{}".into(),
127 },
128 InspectorRequest::Rewind { steps: 3 },
129 InspectorRequest::Simulate {
130 action: "attack".into(),
131 },
132 InspectorRequest::GetHistory,
133 ];
134 assert_eq!(requests.len(), 9);
135 }
136}