liminal_cli/network/
call.rs1use serde_json::{Value, json};
5
6use super::Rail;
7use crate::mcp::tools::ToolResult;
8
9#[derive(Clone, PartialEq, Eq)]
11pub enum Outcome {
12 Reply(Value),
14 Sent,
16 Refused(String),
18}
19
20pub const PASS_FIELD: &str = "pass";
22
23pub fn with_pass<R: Rail>(rail: &R, input: &Value, audience: &str) -> Result<Value, String> {
30 let Value::Object(object) = input else {
31 return Err("pass_for needs an object input".to_owned());
32 };
33 if object.contains_key(PASS_FIELD) {
34 return Err(format!(
35 "the input already carries {PASS_FIELD}; with pass_for the ticket puts it in"
36 ));
37 }
38 let pass = rail.pass_for(audience).map_err(|error| error.to_string())?;
39 let mut object = object.clone();
40 object.insert(PASS_FIELD.to_owned(), Value::String(pass));
41 Ok(Value::Object(object))
42}
43
44pub fn request<R: Rail>(
48 rail: &R,
49 subject: &str,
50 input: &Value,
51 reply: bool,
52 pass_for: Option<&str>,
53) -> Outcome {
54 let minted;
55 let input = match pass_for {
56 None => input,
57 Some(audience) => match with_pass(rail, input, audience) {
58 Ok(carrying) => {
59 minted = carrying;
60 &minted
61 }
62 Err(words) => return Outcome::Refused(words),
63 },
64 };
65 let bytes = serde_json::to_vec(input).map_or(usize::MAX, |bytes| bytes.len());
66 if bytes > rail.request_bytes() {
67 return Outcome::Refused(format!(
68 "the request is over {} bytes and was not sent",
69 rail.request_bytes()
70 ));
71 }
72 match rail.request(subject, input, reply) {
73 Ok(Some(answer)) => Outcome::Reply(answer),
74 Ok(None) => Outcome::Sent,
75 Err(error) => Outcome::Refused(error.to_string()),
76 }
77}
78
79pub fn call<R: Rail>(
81 rail: &R,
82 subject: &str,
83 input: &Value,
84 reply: bool,
85 pass_for: Option<&str>,
86) -> ToolResult {
87 match request(rail, subject, input, reply, pass_for) {
88 Outcome::Reply(answer) => ToolResult::ok(answer),
89 Outcome::Sent => ToolResult::ok(json!({ "subject": subject, "sent": true })),
90 Outcome::Refused(words) => ToolResult::failed(&words, json!({ "subject": subject })),
91 }
92}
93
94#[cfg(test)]
95#[path = "call_tests.rs"]
96mod tests;
97
98impl std::fmt::Debug for Outcome {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 f.write_str(match self {
101 Self::Reply(_) => "Reply",
102 Self::Sent => "Sent",
103 Self::Refused(_) => "Refused",
104 })
105 }
106}