1use super::*;
8
9pub(crate) fn require_agent_env(local_url: &str, auth_key: &str, block_id: &str) -> Result<()> {
13 if local_url.is_empty() || auth_key.is_empty() {
14 anyhow::bail!(
15 "AGENTMUX_LOCAL_URL and AGENTMUX_AUTH_KEY must be set. \
16 Is this agent pane opened via AgentMux?"
17 );
18 }
19 if block_id.is_empty() {
20 anyhow::bail!(
21 "neither AGENTMUX_AGENT_BUS_ID nor AGENTMUX_BLOCKID is set \
22 — cannot resolve this agent's context. Is this agent pane opened via AgentMux?"
23 );
24 }
25 Ok(())
26}
27
28pub(crate) async fn srv_get_json(
37 client: &reqwest::Client,
38 url: &str,
39 auth_key: &str,
40 query: &[(&str, String)],
41 what: &str,
42) -> Result<Value> {
43 let resp = client
44 .get(url)
45 .header(AUTH_KEY_HEADER, auth_key)
46 .query(query)
47 .send()
48 .await
49 .map_err(|e| anyhow::anyhow!(describe_transport_error(what, url, &e)))?;
50 let status = resp.status();
51 let text = resp
52 .text()
53 .await
54 .map_err(|e| anyhow::anyhow!("{what}: reading AgentMux's response failed: {e}"))?;
55 if !status.is_success() {
56 anyhow::bail!(describe_http_error(what, status, &text));
57 }
58 serde_json::from_str(&text).map_err(|e| {
59 anyhow::anyhow!("{what}: AgentMux answered HTTP {status} but not with JSON ({e})")
60 })
61}
62
63pub(crate) async fn srv_post_json(
66 client: &reqwest::Client,
67 url: &str,
68 auth_key: &str,
69 body: &Value,
70 what: &str,
71) -> Result<Value> {
72 let resp = client
73 .post(url)
74 .header(AUTH_KEY_HEADER, auth_key)
75 .json(body)
76 .send()
77 .await
78 .map_err(|e| anyhow::anyhow!(describe_transport_error(what, url, &e)))?;
79 let status = resp.status();
80 let text = resp
81 .text()
82 .await
83 .map_err(|e| anyhow::anyhow!("{what}: reading AgentMux's response failed: {e}"))?;
84 if !status.is_success() {
85 anyhow::bail!(describe_http_error(what, status, &text));
86 }
87 serde_json::from_str(&text).map_err(|e| {
88 anyhow::anyhow!("{what}: AgentMux answered HTTP {status} but not with JSON ({e})")
89 })
90}
91
92pub(crate) fn describe_transport_error(what: &str, url: &str, e: &reqwest::Error) -> String {
93 let mut causes = String::new();
94 let mut source = std::error::Error::source(e);
95 while let Some(s) = source {
96 causes.push_str(&format!(": {s}"));
97 source = s.source();
98 }
99 if e.is_timeout() {
100 format!(
101 "{what} timed out: AgentMux didn't answer in time. This is not an empty result — retry, \
102 or ask for less"
103 )
104 } else if e.is_connect() {
105 format!(
106 "{what}: can't reach AgentMux at {url}{causes}. Is it still running? An agent started \
107 by an AgentMux that has since restarted must be reopened."
108 )
109 } else {
110 format!("{what}: the request to AgentMux failed: {e}{causes}")
111 }
112}
113
114pub(crate) fn describe_http_error(what: &str, status: reqwest::StatusCode, body: &str) -> String {
115 let detail = serde_json::from_str::<Value>(body)
116 .ok()
117 .and_then(|v| v.get("error").and_then(Value::as_str).map(str::to_string))
118 .unwrap_or_else(|| {
119 let body = body.trim();
120 if body.is_empty() {
121 "(no body)".to_string()
122 } else {
123 body.chars().take(300).collect()
124 }
125 });
126 let hint = match status.as_u16() {
127 401 => {
128 " — AgentMux rejected this process's auth key (AGENTMUX_AUTH_KEY). The agent was \
129 probably started by an AgentMux that has since restarted; reopen it."
130 }
131 503 => " — temporary; retry shortly.",
132 _ => "",
133 };
134 format!("{what} failed (HTTP {status}): {detail}{hint}")
135}
136
137pub(crate) const SHELL_CREATE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(150);