1use std::sync::Arc;
6
7use serde_json::{Map, Value};
8
9use crate::agent::AcpAgent;
10use crate::errors::{AcpError, AcpResult};
11use crate::messages::DeliveryMode;
12use crate::overlay::{
13 BusinessHandler, OverlayInboundAdapter, OverlayOutboundAdapter, PassthroughHandler,
14 invalid_overlay_request,
15};
16
17pub const WELL_KNOWN_CACHE_CONTROL: &str = "public, max-age=300";
18
19#[derive(Debug, Clone)]
20pub struct OverlayHttpResponse {
21 pub status_code: u16,
22 pub body: Map<String, Value>,
23}
24
25#[derive(Clone)]
26pub struct OverlayFrameworkRuntime {
27 pub agent: AcpAgent,
28 pub base_url: String,
29 pub inbound_adapter: OverlayInboundAdapter,
30 pub outbound_adapter: OverlayOutboundAdapter,
31}
32
33#[derive(Clone)]
34pub struct OverlayConfig {
35 pub agent: AcpAgent,
36 pub base_url: String,
37 pub passthrough_handler: Option<PassthroughHandler>,
38}
39
40#[derive(Clone)]
41pub struct OverlayClient {
42 pub agent: AcpAgent,
43 pub outbound_adapter: OverlayOutboundAdapter,
44}
45
46impl OverlayFrameworkRuntime {
47 pub fn create(
48 agent: AcpAgent,
49 base_url: &str,
50 business_handler: BusinessHandler,
51 passthrough_handler: Option<PassthroughHandler>,
52 ) -> AcpResult<Self> {
53 let normalized_base_url = base_url.trim();
54 if normalized_base_url.is_empty() {
55 return Err(AcpError::Validation("base_url is required".to_string()));
56 }
57 let inbound_adapter =
58 OverlayInboundAdapter::new(agent.clone(), business_handler, passthrough_handler);
59 let outbound_adapter = OverlayOutboundAdapter::new(agent.clone());
60 Ok(Self {
61 agent,
62 base_url: normalized_base_url.trim_end_matches('/').to_string(),
63 inbound_adapter,
64 outbound_adapter,
65 })
66 }
67
68 pub fn handle_message_body(&mut self, body: &Value) -> OverlayHttpResponse {
69 let Some(body) = body.as_object() else {
70 return OverlayHttpResponse {
71 status_code: 400,
72 body: invalid_overlay_request("Expected JSON object request body"),
73 };
74 };
75 match self.inbound_adapter.handle_request(body) {
76 Ok(response) => OverlayHttpResponse {
77 status_code: 200,
78 body: response,
79 },
80 Err(exc) => OverlayHttpResponse {
81 status_code: 400,
82 body: invalid_overlay_request(exc.to_string()),
83 },
84 }
85 }
86
87 pub fn well_known_document(&self) -> AcpResult<Map<String, Value>> {
88 self.agent
89 .build_well_known_document(Some(&self.base_url), None)
90 }
91
92 pub fn well_known_headers() -> Map<String, Value> {
93 let mut headers = Map::new();
94 headers.insert(
95 "Cache-Control".to_string(),
96 Value::String(WELL_KNOWN_CACHE_CONTROL.to_string()),
97 );
98 headers
99 }
100
101 pub fn identity_document_payload(&self) -> Map<String, Value> {
102 let mut payload = Map::new();
103 payload.insert(
104 "identity_document".to_string(),
105 Value::Object(self.agent.identity_document.clone()),
106 );
107 payload
108 }
109
110 #[allow(clippy::too_many_arguments)]
111 pub fn send_business_payload(
112 &mut self,
113 payload: Map<String, Value>,
114 target_base_url: Option<&str>,
115 recipient_agent_id: Option<&str>,
116 context: Option<String>,
117 delivery_mode: Option<DeliveryMode>,
118 expires_in_seconds: i64,
119 ) -> AcpResult<Map<String, Value>> {
120 let result = self.outbound_adapter.send_business_payload(
121 payload,
122 target_base_url,
123 recipient_agent_id,
124 context,
125 delivery_mode,
126 expires_in_seconds,
127 )?;
128 Ok(send_result_to_map(result))
129 }
130
131 pub fn send_acp(
132 &mut self,
133 target_url: &str,
134 payload: Map<String, Value>,
135 recipient_agent_id: Option<&str>,
136 context: Option<String>,
137 delivery_mode: Option<DeliveryMode>,
138 expires_in_seconds: i64,
139 ) -> AcpResult<Map<String, Value>> {
140 self.send_business_payload(
141 payload,
142 Some(target_url),
143 recipient_agent_id,
144 context,
145 delivery_mode,
146 expires_in_seconds,
147 )
148 }
149
150 pub fn handle(
151 request_body: &Value,
152 business_handler: BusinessHandler,
153 config: OverlayConfig,
154 ) -> OverlayHttpResponse {
155 let runtime = Self::create(
156 config.agent,
157 &config.base_url,
158 business_handler,
159 config.passthrough_handler,
160 );
161 match runtime {
162 Ok(mut runtime) => runtime.handle_message_body(request_body),
163 Err(exc) => OverlayHttpResponse {
164 status_code: 400,
165 body: invalid_overlay_request(exc.to_string()),
166 },
167 }
168 }
169}
170
171impl OverlayClient {
172 pub fn create(agent: AcpAgent) -> Self {
173 Self {
174 outbound_adapter: OverlayOutboundAdapter::new(agent.clone()),
175 agent,
176 }
177 }
178
179 #[allow(clippy::too_many_arguments)]
180 pub fn send_acp(
181 &mut self,
182 target_url: &str,
183 payload: Map<String, Value>,
184 recipient_agent_id: Option<&str>,
185 context: Option<String>,
186 delivery_mode: Option<DeliveryMode>,
187 expires_in_seconds: i64,
188 ) -> AcpResult<Map<String, Value>> {
189 let result = self.outbound_adapter.send_business_payload(
190 payload,
191 Some(target_url),
192 recipient_agent_id,
193 context,
194 delivery_mode,
195 expires_in_seconds,
196 )?;
197 Ok(send_result_to_map(result))
198 }
199}
200
201pub fn acp_overlay_inbound(
202 mut agent: AcpAgent,
203 handler: Arc<dyn Fn(&Map<String, Value>) -> Option<Map<String, Value>> + Send + Sync>,
204 passthrough: bool,
205) -> impl FnMut(&Map<String, Value>) -> AcpResult<Map<String, Value>> {
206 let passthrough_handler = if passthrough {
207 Some(handler.clone())
208 } else {
209 None
210 };
211 move |payload: &Map<String, Value>| {
212 let mut inbound =
213 OverlayInboundAdapter::new(agent.clone(), handler.clone(), passthrough_handler.clone());
214 let response = inbound.handle_request(payload);
215 agent = inbound.agent;
216 response
217 }
218}
219
220fn send_result_to_map(result: crate::overlay::OverlaySendResult) -> Map<String, Value> {
221 let mut map = Map::new();
222 if let Some(target) = result.target {
223 map.insert(
224 "target".to_string(),
225 serde_json::json!({
226 "agent_id": target.agent_id,
227 "base_url": target.base_url,
228 "well_known_url": target.well_known_url,
229 "identity_document_url": target.identity_document_url,
230 }),
231 );
232 } else {
233 map.insert("target".to_string(), Value::Null);
234 }
235 map.insert(
236 "send_result".to_string(),
237 serde_json::to_value(&result.send_result).unwrap_or(Value::Null),
238 );
239 map
240}