1use std::sync::Arc;
6
7use serde_json::{Map, Value};
8
9use crate::agent::{AcpAgent, InboundHandlerFn};
10use crate::errors::{AcpError, AcpResult, FailReason};
11use crate::messages::{DeliveryMode, MessageClass, SendResult};
12
13pub type BusinessHandler =
14 Arc<dyn Fn(&Map<String, Value>) -> Option<Map<String, Value>> + Send + Sync>;
15pub type PassthroughHandler =
16 Arc<dyn Fn(&Map<String, Value>) -> Option<Map<String, Value>> + Send + Sync>;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct OverlayTarget {
20 pub agent_id: String,
21 pub base_url: String,
22 pub well_known_url: String,
23 pub identity_document_url: String,
24}
25
26#[derive(Debug, Clone)]
27pub struct OverlaySendResult {
28 pub target: Option<OverlayTarget>,
29 pub send_result: SendResult,
30}
31
32#[derive(Clone)]
33pub struct OverlayInboundAdapter {
34 pub agent: AcpAgent,
35 pub business_handler: BusinessHandler,
36 pub passthrough_handler: Option<PassthroughHandler>,
37}
38
39impl OverlayInboundAdapter {
40 pub fn new(
41 agent: AcpAgent,
42 business_handler: BusinessHandler,
43 passthrough_handler: Option<PassthroughHandler>,
44 ) -> Self {
45 Self {
46 agent,
47 business_handler,
48 passthrough_handler,
49 }
50 }
51
52 pub fn handle_request(&mut self, body: &Map<String, Value>) -> AcpResult<Map<String, Value>> {
53 if !is_acp_http_message(body) {
54 if let Some(passthrough) = &self.passthrough_handler {
55 let payload = passthrough(body).unwrap_or_default();
56 let mut response = Map::new();
57 response.insert("mode".to_string(), Value::String("passthrough".to_string()));
58 response.insert("payload".to_string(), Value::Object(payload));
59 return Ok(response);
60 }
61 return Err(AcpError::Validation(
62 "Request is not an ACP message and no passthrough_handler is configured"
63 .to_string(),
64 ));
65 }
66 let business_handler = self.business_handler.clone();
67 let inbound_handler =
68 move |payload: &Map<String, Value>, _envelope: &crate::messages::Envelope| {
69 business_handler(payload)
70 };
71 let inbound = self
72 .agent
73 .receive(body, Some(&inbound_handler as &InboundHandlerFn));
74 let mut response = Map::new();
75 response.insert("mode".to_string(), Value::String("acp".to_string()));
76 response.insert(
77 "acp_result".to_string(),
78 serde_json::to_value(&inbound).unwrap_or(Value::Null),
79 );
80 response.insert(
81 "state".to_string(),
82 Value::String(format!("{:?}", inbound.state).to_uppercase()),
83 );
84 if let Some(reason_code) = inbound.reason_code {
85 response.insert("reason_code".to_string(), Value::String(reason_code));
86 } else {
87 response.insert("reason_code".to_string(), Value::Null);
88 }
89 if let Some(detail) = inbound.detail {
90 response.insert("detail".to_string(), Value::String(detail));
91 } else {
92 response.insert("detail".to_string(), Value::Null);
93 }
94 response.insert(
95 "response_message".to_string(),
96 inbound
97 .response_message
98 .map(Value::Object)
99 .unwrap_or(Value::Null),
100 );
101 Ok(response)
102 }
103}
104
105#[derive(Clone)]
106pub struct OverlayOutboundAdapter {
107 pub agent: AcpAgent,
108}
109
110impl OverlayOutboundAdapter {
111 pub fn new(agent: AcpAgent) -> Self {
112 Self { agent }
113 }
114
115 pub fn resolve_target(
116 &mut self,
117 target_base_url: &str,
118 expected_agent_id: Option<&str>,
119 ) -> AcpResult<OverlayTarget> {
120 let resolved = self
121 .agent
122 .resolve_well_known(target_base_url, expected_agent_id)?;
123 let well_known = resolved
124 .get("well_known")
125 .and_then(Value::as_object)
126 .ok_or_else(|| {
127 AcpError::Discovery(
128 "Resolved well-known metadata missing well_known object".to_string(),
129 )
130 })?;
131 let identity_document = resolved
132 .get("identity_document")
133 .and_then(Value::as_object)
134 .ok_or_else(|| {
135 AcpError::Discovery(
136 "Resolved well-known metadata missing identity_document object".to_string(),
137 )
138 })?;
139 let agent_id = identity_document
140 .get("agent_id")
141 .and_then(Value::as_str)
142 .map(str::trim)
143 .filter(|v| !v.is_empty())
144 .ok_or_else(|| {
145 AcpError::Discovery(
146 "Resolved well-known metadata did not include a valid identity_document.agent_id"
147 .to_string(),
148 )
149 })?;
150 let identity_document_url = well_known
151 .get("identity_document")
152 .and_then(Value::as_str)
153 .map(str::trim)
154 .filter(|v| !v.is_empty())
155 .ok_or_else(|| {
156 AcpError::Discovery(
157 "Resolved well-known metadata did not include a valid identity_document URL"
158 .to_string(),
159 )
160 })?;
161 Ok(OverlayTarget {
162 agent_id: agent_id.to_string(),
163 base_url: target_base_url.trim_end_matches('/').to_string(),
164 well_known_url: resolved
165 .get("well_known_url")
166 .and_then(Value::as_str)
167 .unwrap_or_default()
168 .to_string(),
169 identity_document_url: identity_document_url.to_string(),
170 })
171 }
172
173 #[allow(clippy::too_many_arguments)]
174 pub fn send_business_payload(
175 &mut self,
176 payload: Map<String, Value>,
177 target_base_url: Option<&str>,
178 recipient_agent_id: Option<&str>,
179 context: Option<String>,
180 delivery_mode: Option<DeliveryMode>,
181 expires_in_seconds: i64,
182 ) -> AcpResult<OverlaySendResult> {
183 let mut target = None;
184 let mut resolved_recipient = recipient_agent_id.map(str::to_string);
185 if let Some(target_base_url) = target_base_url {
186 let resolved_target = self.resolve_target(target_base_url, recipient_agent_id)?;
187 if resolved_recipient.is_none() {
188 resolved_recipient = Some(resolved_target.agent_id.clone());
189 }
190 target = Some(resolved_target);
191 }
192 let recipient = resolved_recipient.ok_or_else(|| {
193 AcpError::Validation(
194 "send_business_payload requires recipient_agent_id or target_base_url for well-known bootstrap"
195 .to_string(),
196 )
197 })?;
198 let send_result = self.agent.send(
199 vec![recipient],
200 payload,
201 context,
202 MessageClass::Send,
203 expires_in_seconds,
204 None,
205 None,
206 delivery_mode,
207 )?;
208 Ok(OverlaySendResult {
209 target,
210 send_result,
211 })
212 }
213}
214
215pub fn is_acp_http_message(body: &Map<String, Value>) -> bool {
216 body.get("envelope").and_then(Value::as_object).is_some()
217 && body.get("protected").and_then(Value::as_object).is_some()
218}
219
220pub fn invalid_overlay_request(detail: impl Into<String>) -> Map<String, Value> {
221 let mut response = Map::new();
222 response.insert("mode".to_string(), Value::String("invalid".to_string()));
223 response.insert("state".to_string(), Value::String("FAILED".to_string()));
224 response.insert(
225 "reason_code".to_string(),
226 Value::String(FailReason::PolicyRejected.as_str().to_string()),
227 );
228 response.insert("detail".to_string(), Value::String(detail.into()));
229 response.insert("response_message".to_string(), Value::Null);
230 response
231}