agent_client_protocol/
util.rs1use futures::{
4 future::BoxFuture,
5 stream::{Stream, StreamExt},
6};
7
8mod typed;
9pub use typed::{MatchDispatch, MatchDispatchFrom};
10
11pub(crate) fn redacted_error(code: impl Into<i32>) -> impl std::fmt::Debug {
17 struct RedactedError {
18 code: i32,
19 reason: &'static str,
20 }
21
22 impl std::fmt::Debug for RedactedError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("Error")
25 .field("code", &self.code)
26 .field("reason", &self.reason)
27 .finish()
28 }
29 }
30
31 let code = code.into();
32 let reason = match code {
33 -32700 => "Parse error",
34 -32600 => "Invalid request",
35 -32601 => "Method not found",
36 -32602 => "Invalid params",
37 -32603 => "Internal error",
38 _ => "Protocol or application error",
39 };
40 RedactedError { code, reason }
41}
42
43fn redact_env_and_header_values(value: &mut serde_json::Value) {
51 match value {
52 serde_json::Value::Object(object) => {
53 for (key, field) in object.iter_mut() {
54 if key == "env" || key == "headers" {
55 redact_name_value_pairs(field);
56 }
57 redact_env_and_header_values(field);
58 }
59 }
60 serde_json::Value::Array(values) => {
61 values.iter_mut().for_each(redact_env_and_header_values);
62 }
63 _ => {}
64 }
65}
66
67fn redact_name_value_pairs(field: &mut serde_json::Value) {
69 let redacted = || serde_json::Value::String("[REDACTED]".to_owned());
70 match field {
71 serde_json::Value::Array(entries) => {
72 for entry in entries {
73 if let Some(value) = entry.get_mut("value") {
74 *value = redacted();
75 }
76 }
77 }
78 serde_json::Value::Object(entries) => {
79 for value in entries.values_mut() {
80 *value = redacted();
81 }
82 }
83 _ => {}
84 }
85}
86
87pub(crate) fn redacted_json_line(line: &str) -> impl std::fmt::Display + '_ {
92 struct RedactedJsonLine<'a>(&'a str);
93
94 impl std::fmt::Display for RedactedJsonLine<'_> {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match serde_json::from_str::<serde_json::Value>(self.0) {
97 Ok(mut value) => {
98 redact_env_and_header_values(&mut value);
99 write!(f, "{value}")
100 }
101 Err(_) => write!(f, "<{} bytes of invalid JSON omitted>", self.0.len()),
102 }
103 }
104 }
105
106 RedactedJsonLine(line)
107}
108
109pub fn json_cast<N, M>(params: N) -> Result<M, crate::Error>
120where
121 N: serde::Serialize,
122 M: serde::de::DeserializeOwned,
123{
124 let json = serde_json::to_value(params).map_err(|e| {
125 crate::Error::parse_error().data(serde_json::json!({
126 "error": e.to_string(),
127 "phase": "serialization"
128 }))
129 })?;
130 let m = serde_json::from_value(json.clone()).map_err(|e| {
131 crate::Error::parse_error().data(serde_json::json!({
132 "error": e.to_string(),
133 "json": json,
134 "phase": "deserialization"
135 }))
136 })?;
137 Ok(m)
138}
139
140pub fn json_cast_params<N, M>(params: N) -> Result<M, crate::Error>
150where
151 N: serde::Serialize,
152 M: serde::de::DeserializeOwned,
153{
154 let json = serde_json::to_value(params).map_err(|e| {
155 crate::Error::internal_error().data(serde_json::json!({
156 "error": e.to_string(),
157 "phase": "serialization"
158 }))
159 })?;
160 let m = serde_json::from_value(json.clone()).map_err(|e| {
161 crate::Error::invalid_params().data(serde_json::json!({
162 "error": e.to_string(),
163 "json": json,
164 "phase": "deserialization"
165 }))
166 })?;
167 Ok(m)
168}
169
170pub(crate) fn internal_error(message: impl ToString) -> crate::Error {
172 crate::Error::internal_error().data(message.to_string())
173}
174
175pub fn initialize_params_for_version(
192 params: &serde_json::Value,
193 selected: crate::schema::ProtocolVersion,
194) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
195 use crate::schema::ProtocolVersion;
196
197 let invalid_version = || {
198 crate::Error::invalid_params()
199 .data("initialize.protocolVersion must be a valid ACP protocol version")
200 };
201 let serde_json::Value::Object(object) = params else {
202 return Err(invalid_version());
203 };
204 let requested: ProtocolVersion = object
205 .get("protocolVersion")
206 .cloned()
207 .and_then(|version| serde_json::from_value(version).ok())
208 .ok_or_else(invalid_version)?;
209
210 let mut rewritten = if requested <= selected {
211 object.clone()
212 } else if selected == ProtocolVersion::V1 {
213 crate::jsonrpc::normalize_v1_initialize_request(params)?
214 } else {
215 #[cfg(feature = "unstable_protocol_v2")]
216 if selected == ProtocolVersion::V2 {
217 return crate::jsonrpc::normalize_newer_v2_initialize_request(object);
218 }
219 return Err(crate::Error::invalid_request().data(format!(
220 "cannot rewrite an initialize request for ACP protocol version {requested} to version {selected}",
221 )));
222 };
223 rewritten.insert(
224 "protocolVersion".into(),
225 serde_json::to_value(selected).map_err(crate::Error::into_internal_error)?,
226 );
227 Ok(rewritten)
228}
229
230pub(crate) fn batch_not_supported() -> crate::Error {
235 crate::Error::invalid_request()
236 .data("JSON-RPC batches are not supported; send each message individually")
237}
238
239pub(crate) fn instrumented_with_connection_name<F>(
240 name: String,
241 task: F,
242) -> tracing::instrument::Instrumented<F> {
243 use tracing::Instrument;
244
245 task.instrument(tracing::info_span!("connection", name = name))
246}
247
248pub(crate) async fn instrument_with_connection_name<R>(
249 name: Option<String>,
250 task: impl Future<Output = R>,
251) -> R {
252 if let Some(name) = name {
253 instrumented_with_connection_name(name.clone(), task).await
254 } else {
255 task.await
256 }
257}
258
259pub fn run_until<T, E>(
265 background: impl Future<Output = Result<(), E>>,
266 foreground: impl Future<Output = Result<T, E>>,
267) -> impl Future<Output = Result<T, E>> {
268 use futures::future::{Either, select};
269 use std::pin::pin;
270
271 Box::pin(async move {
272 match select(pin!(background), pin!(foreground)).await {
273 Either::Left((bg_result, fg_future)) => {
274 bg_result?; fg_future.await
277 }
278 Either::Right((fg_result, _bg_future)) => {
279 fg_result
281 }
282 }
283 })
284}
285
286pub(crate) async fn process_stream_concurrently<T, F>(
295 stream: impl Stream<Item = T>,
296 process_fn: F,
297 process_fn_hack: impl for<'a> Fn(&'a F, T) -> BoxFuture<'a, Result<(), crate::Error>>,
298) -> Result<(), crate::Error>
299where
300 F: AsyncFn(T) -> Result<(), crate::Error>,
301{
302 use std::pin::pin;
303
304 use futures::stream::{FusedStream, FuturesUnordered};
305 use futures_concurrency::future::Race;
306
307 enum Event<T> {
308 NewItem(Option<T>),
309 FutureCompleted(Option<Result<(), crate::Error>>),
310 }
311
312 let mut stream = pin!(stream.fuse());
313 let mut futures: FuturesUnordered<_> = FuturesUnordered::new();
314
315 loop {
316 if futures.is_empty() {
318 match stream.next().await {
319 Some(item) => futures.push(process_fn_hack(&process_fn, item)),
320 None => return Ok(()),
321 }
322 continue;
323 }
324
325 if stream.is_terminated() {
327 while let Some(result) = futures.next().await {
328 result?;
329 }
330 return Ok(());
331 }
332
333 let event = (async { Event::NewItem(stream.next().await) }, async {
335 Event::FutureCompleted(futures.next().await)
336 })
337 .race()
338 .await;
339
340 match event {
341 Event::NewItem(Some(item)) => {
342 futures.push(process_fn_hack(&process_fn, item));
343 }
344 Event::FutureCompleted(Some(result)) => {
345 result?;
346 }
347 Event::NewItem(None) | Event::FutureCompleted(None) => {
348 }
351 }
352 }
353}