Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:33:31.220Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

proto_client.rs

693 lines · 25.9 KB · rust
1use anyhow::{Context, Result};
2use collections::{HashMap, TypeIdHashMap};
3use futures::{
4    Future, FutureExt as _, Stream, StreamExt as _,
5    channel::oneshot,
6    future::{BoxFuture, LocalBoxFuture},
7    stream::BoxStream,
8};
9use gpui::{AnyEntity, AnyWeakEntity, AsyncApp, BackgroundExecutor, Entity, FutureExt as _};
10use parking_lot::Mutex;
11use proto::{
12    AnyTypedEnvelope, EntityMessage, Envelope, EnvelopedMessage, LspRequestId, LspRequestMessage,
13    RequestMessage, TypedEnvelope, error::ErrorExt as _,
14};
15use std::{
16    any::{Any, TypeId},
17    sync::{
18        Arc, OnceLock,
19        atomic::{self, AtomicU64},
20    },
21    time::Duration,
22};
23
24#[derive(Debug, Clone)]
25pub struct AnyProtoClient(Arc<State>);
26
27type RequestIds = Arc<
28    Mutex<
29        HashMap<
30            LspRequestId,
31            oneshot::Sender<
32                Result<
33                    Option<TypedEnvelope<Vec<proto::ProtoLspResponse<Box<dyn AnyTypedEnvelope>>>>>,
34                >,
35            >,
36        >,
37    >,
38>;
39
40static NEXT_LSP_REQUEST_ID: OnceLock<Arc<AtomicU64>> = OnceLock::new();
41static REQUEST_IDS: OnceLock<RequestIds> = OnceLock::new();
42
43struct State {
44    client: Arc<dyn ProtoClient>,
45    next_lsp_request_id: Arc<AtomicU64>,
46    request_ids: RequestIds,
47}
48
49impl std::fmt::Debug for State {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        f.debug_struct("State")
52            .field("next_lsp_request_id", &self.next_lsp_request_id)
53            .field("request_ids", &self.request_ids)
54            .finish_non_exhaustive()
55    }
56}
57
58pub trait ProtoClient: Send + Sync {
59    fn request(
60        &self,
61        envelope: Envelope,
62        request_type: &'static str,
63    ) -> BoxFuture<'static, Result<Envelope>>;
64
65    fn request_stream(
66        &self,
67        envelope: Envelope,
68        request_type: &'static str,
69    ) -> BoxFuture<'static, Result<BoxStream<'static, Result<Envelope>>>> {
70        async move {
71            anyhow::bail!(
72                "stream requests are not supported for {request_type}: {:?}",
73                envelope.payload
74            )
75        }
76        .boxed()
77    }
78
79    fn send(&self, envelope: Envelope, message_type: &'static str) -> Result<()>;
80
81    fn send_response(&self, envelope: Envelope, message_type: &'static str) -> Result<()>;
82
83    fn message_handler_set(&self) -> &parking_lot::Mutex<ProtoMessageHandlerSet>;
84
85    fn is_via_collab(&self) -> bool;
86    fn has_wsl_interop(&self) -> bool;
87}
88
89#[derive(Default)]
90pub struct ProtoMessageHandlerSet {
91    pub entity_types_by_message_type: TypeIdHashMap<TypeId>,
92    pub entities_by_type_and_remote_id: HashMap<(TypeId, u64), EntityMessageSubscriber>,
93    pub entity_id_extractors: TypeIdHashMap<fn(&dyn AnyTypedEnvelope) -> u64>,
94    pub entities_by_message_type: TypeIdHashMap<AnyWeakEntity>,
95    pub message_handlers: TypeIdHashMap<ProtoMessageHandler>,
96}
97
98pub type ProtoMessageHandler = Arc<
99    dyn Send
100        + Sync
101        + Fn(
102            AnyEntity,
103            Box<dyn AnyTypedEnvelope>,
104            AnyProtoClient,
105            AsyncApp,
106        ) -> LocalBoxFuture<'static, Result<()>>,
107>;
108
109impl ProtoMessageHandlerSet {
110    pub fn clear(&mut self) {
111        self.message_handlers.clear();
112        self.entities_by_message_type.clear();
113        self.entities_by_type_and_remote_id.clear();
114        self.entity_id_extractors.clear();
115    }
116
117    fn add_message_handler(
118        &mut self,
119        message_type_id: TypeId,
120        entity: gpui::AnyWeakEntity,
121        handler: ProtoMessageHandler,
122    ) {
123        self.entities_by_message_type
124            .insert(message_type_id, entity);
125        let prev_handler = self.message_handlers.insert(message_type_id, handler);
126        if prev_handler.is_some() {
127            panic!("registered handler for the same message twice");
128        }
129    }
130
131    fn add_entity_message_handler(
132        &mut self,
133        message_type_id: TypeId,
134        entity_type_id: TypeId,
135        entity_id_extractor: fn(&dyn AnyTypedEnvelope) -> u64,
136        handler: ProtoMessageHandler,
137    ) {
138        self.entity_id_extractors
139            .entry(message_type_id)
140            .or_insert(entity_id_extractor);
141        self.entity_types_by_message_type
142            .insert(message_type_id, entity_type_id);
143        let prev_handler = self.message_handlers.insert(message_type_id, handler);
144        if prev_handler.is_some() {
145            panic!("registered handler for the same message twice");
146        }
147    }
148
149    pub fn handle_message(
150        this: &parking_lot::Mutex<Self>,
151        message: Box<dyn AnyTypedEnvelope>,
152        client: AnyProtoClient,
153        cx: AsyncApp,
154    ) -> Option<LocalBoxFuture<'static, Result<()>>> {
155        let payload_type_id = message.payload_type_id();
156        let mut this = this.lock();
157        let handler = this.message_handlers.get(&payload_type_id)?.clone();
158        let entity = if let Some(entity) = this.entities_by_message_type.get(&payload_type_id) {
159            entity.upgrade()?
160        } else {
161            let extract_entity_id = *this.entity_id_extractors.get(&payload_type_id)?;
162            let entity_type_id = *this.entity_types_by_message_type.get(&payload_type_id)?;
163            let entity_id = (extract_entity_id)(message.as_ref());
164            match this
165                .entities_by_type_and_remote_id
166                .get_mut(&(entity_type_id, entity_id))?
167            {
168                EntityMessageSubscriber::Pending(pending) => {
169                    pending.push(message);
170                    return None;
171                }
172                EntityMessageSubscriber::Entity { handle } => handle.upgrade()?,
173            }
174        };
175        drop(this);
176        Some(handler(entity, message, client, cx))
177    }
178}
179
180pub enum EntityMessageSubscriber {
181    Entity { handle: AnyWeakEntity },
182    Pending(Vec<Box<dyn AnyTypedEnvelope>>),
183}
184
185impl std::fmt::Debug for EntityMessageSubscriber {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        match self {
188            EntityMessageSubscriber::Entity { handle } => f
189                .debug_struct("EntityMessageSubscriber::Entity")
190                .field("handle", handle)
191                .finish(),
192            EntityMessageSubscriber::Pending(vec) => f
193                .debug_struct("EntityMessageSubscriber::Pending")
194                .field(
195                    "envelopes",
196                    &vec.iter()
197                        .map(|envelope| envelope.payload_type_name())
198                        .collect::<Vec<_>>(),
199                )
200                .finish(),
201        }
202    }
203}
204
205impl<T> From<Arc<T>> for AnyProtoClient
206where
207    T: ProtoClient + 'static,
208{
209    fn from(client: Arc<T>) -> Self {
210        Self::new(client)
211    }
212}
213
214impl AnyProtoClient {
215    pub fn new<T: ProtoClient + 'static>(client: Arc<T>) -> Self {
216        Self(Arc::new(State {
217            client,
218            next_lsp_request_id: NEXT_LSP_REQUEST_ID
219                .get_or_init(|| Arc::new(AtomicU64::new(0)))
220                .clone(),
221            request_ids: REQUEST_IDS.get_or_init(RequestIds::default).clone(),
222        }))
223    }
224
225    pub fn is_via_collab(&self) -> bool {
226        self.0.client.is_via_collab()
227    }
228
229    pub fn request<T: RequestMessage>(
230        &self,
231        request: T,
232    ) -> impl Future<Output = Result<T::Response>> + use<T> {
233        let envelope = request.into_envelope(0, None, None);
234        let response = self.0.client.request(envelope, T::NAME);
235        async move {
236            T::Response::from_envelope(response.await?)
237                .context("received response of the wrong type")
238        }
239    }
240
241    pub fn request_stream<T: RequestMessage>(
242        &self,
243        request: T,
244    ) -> impl Future<Output = Result<BoxStream<'static, Result<T::Response>>>> + use<T> {
245        let envelope = request.into_envelope(0, None, None);
246        let response_stream = self.0.client.request_stream(envelope, T::NAME);
247        async move {
248            Ok(response_stream
249                .await?
250                .map(|response| {
251                    T::Response::from_envelope(response?)
252                        .context("received response of the wrong type")
253                })
254                .boxed())
255        }
256    }
257
258    pub fn send<T: EnvelopedMessage>(&self, request: T) -> Result<()> {
259        let envelope = request.into_envelope(0, None, None);
260        self.0.client.send(envelope, T::NAME)
261    }
262
263    pub fn send_response<T: EnvelopedMessage>(&self, request_id: u32, request: T) -> Result<()> {
264        let envelope = request.into_envelope(0, Some(request_id), None);
265        self.0.client.send(envelope, T::NAME)
266    }
267
268    pub fn request_lsp<T>(
269        &self,
270        project_id: u64,
271        server_id: Option<u64>,
272        timeout: Duration,
273        executor: BackgroundExecutor,
274        request: T,
275    ) -> impl Future<
276        Output = Result<Option<TypedEnvelope<Vec<proto::ProtoLspResponse<T::Response>>>>>,
277    > + use<T>
278    where
279        T: LspRequestMessage,
280    {
281        let new_id = LspRequestId(
282            self.0
283                .next_lsp_request_id
284                .fetch_add(1, atomic::Ordering::Acquire),
285        );
286        let (tx, rx) = oneshot::channel();
287        {
288            self.0.request_ids.lock().insert(new_id, tx);
289        }
290
291        let query = proto::LspQuery {
292            project_id,
293            server_id,
294            lsp_request_id: new_id.0,
295            request: Some(request.to_proto_query()),
296        };
297        let request = self.request(query);
298        let request_ids = self.0.request_ids.clone();
299        async move {
300            match request.await {
301                Ok(_request_enqueued) => {}
302                Err(e) => {
303                    request_ids.lock().remove(&new_id);
304                    return Err(e).context("sending LSP proto request");
305                }
306            }
307
308            let response = rx.with_timeout(timeout, &executor).await;
309            {
310                request_ids.lock().remove(&new_id);
311            }
312            match response {
313                Ok(Ok(response)) => {
314                    let response = response
315                        .context("waiting for LSP proto response")?
316                        .map(|response| {
317                            anyhow::Ok(TypedEnvelope {
318                                payload: response
319                                    .payload
320                                    .into_iter()
321                                    .map(|lsp_response| lsp_response.into_response::<T>())
322                                    .collect::<Result<Vec<_>>>()?,
323                                sender_id: response.sender_id,
324                                original_sender_id: response.original_sender_id,
325                                message_id: response.message_id,
326                                received_at: response.received_at,
327                            })
328                        })
329                        .transpose()
330                        .context("converting LSP proto response")?;
331                    Ok(response)
332                }
333                Err(_cancelled_due_timeout) => Ok(None),
334                Ok(Err(_channel_dropped)) => Ok(None),
335            }
336        }
337    }
338
339    pub fn send_lsp_response<T: LspRequestMessage>(
340        &self,
341        project_id: u64,
342        lsp_request_id: LspRequestId,
343        server_responses: HashMap<u64, T::Response>,
344    ) -> Result<()> {
345        self.send(proto::LspQueryResponse {
346            project_id,
347            lsp_request_id: lsp_request_id.0,
348            responses: server_responses
349                .into_iter()
350                .map(|(server_id, response)| proto::LspResponse {
351                    server_id,
352                    response: Some(T::response_to_proto_query(response)),
353                })
354                .collect(),
355        })
356    }
357
358    pub fn handle_lsp_response(&self, mut envelope: TypedEnvelope<proto::LspQueryResponse>) {
359        let request_id = LspRequestId(envelope.payload.lsp_request_id);
360        let mut response_senders = self.0.request_ids.lock();
361        if let Some(tx) = response_senders.remove(&request_id) {
362            let responses = envelope.payload.responses.drain(..).collect::<Vec<_>>();
363            tx.send(Ok(Some(proto::TypedEnvelope {
364                sender_id: envelope.sender_id,
365                original_sender_id: envelope.original_sender_id,
366                message_id: envelope.message_id,
367                received_at: envelope.received_at,
368                payload: responses
369                    .into_iter()
370                    .filter_map(|response| {
371                        use proto::lsp_response::Response;
372
373                        let server_id = response.server_id;
374                        let response = match response.response? {
375                            Response::GetReferencesResponse(response) => {
376                                to_any_envelope(&envelope, response)
377                            }
378                            Response::GetDocumentColorResponse(response) => {
379                                to_any_envelope(&envelope, response)
380                            }
381                            Response::GetHoverResponse(response) => {
382                                to_any_envelope(&envelope, response)
383                            }
384                            Response::GetCodeActionsResponse(response) => {
385                                to_any_envelope(&envelope, response)
386                            }
387                            Response::GetSignatureHelpResponse(response) => {
388                                to_any_envelope(&envelope, response)
389                            }
390                            Response::GetCodeLensResponse(response) => {
391                                to_any_envelope(&envelope, response)
392                            }
393                            Response::GetDocumentDiagnosticsResponse(response) => {
394                                to_any_envelope(&envelope, response)
395                            }
396                            Response::GetDefinitionResponse(response) => {
397                                to_any_envelope(&envelope, response)
398                            }
399                            Response::GetEditPredictionDefinitionResponse(response) => {
400                                to_any_envelope(&envelope, response)
401                            }
402                            Response::GetDeclarationResponse(response) => {
403                                to_any_envelope(&envelope, response)
404                            }
405                            Response::GetTypeDefinitionResponse(response) => {
406                                to_any_envelope(&envelope, response)
407                            }
408                            Response::GetEditPredictionTypeDefinitionResponse(response) => {
409                                to_any_envelope(&envelope, response)
410                            }
411                            Response::GetImplementationResponse(response) => {
412                                to_any_envelope(&envelope, response)
413                            }
414                            Response::InlayHintsResponse(response) => {
415                                to_any_envelope(&envelope, response)
416                            }
417                            Response::SemanticTokensResponse(response) => {
418                                to_any_envelope(&envelope, response)
419                            }
420                            Response::GetFoldingRangesResponse(response) => {
421                                to_any_envelope(&envelope, response)
422                            }
423                            Response::GetDocumentSymbolsResponse(response) => {
424                                to_any_envelope(&envelope, response)
425                            }
426                            Response::GetDocumentLinksResponse(response) => {
427                                to_any_envelope(&envelope, response)
428                            }
429                        };
430                        Some(proto::ProtoLspResponse {
431                            server_id,
432                            response,
433                        })
434                    })
435                    .collect(),
436            })))
437            .ok();
438        }
439    }
440
441    pub fn add_request_handler<M, E, H, F>(&self, entity: gpui::WeakEntity<E>, handler: H)
442    where
443        M: RequestMessage,
444        E: 'static,
445        H: 'static + Sync + Fn(Entity<E>, TypedEnvelope<M>, AsyncApp) -> F + Send + Sync,
446        F: 'static + Future<Output = Result<M::Response>>,
447    {
448        self.0
449            .client
450            .message_handler_set()
451            .lock()
452            .add_message_handler(
453                TypeId::of::<M>(),
454                entity.into(),
455                Arc::new(move |entity, envelope, client, cx| {
456                    let entity = entity.downcast::<E>().unwrap();
457                    let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
458                    let request_id = envelope.message_id();
459                    handler(entity, *envelope, cx)
460                        .then(move |result| async move {
461                            match result {
462                                Ok(response) => {
463                                    client.send_response(request_id, response)?;
464                                    Ok(())
465                                }
466                                Err(error) => {
467                                    client.send_response(request_id, error.to_proto())?;
468                                    Err(error)
469                                }
470                            }
471                        })
472                        .boxed_local()
473                }),
474            )
475    }
476
477    pub fn add_entity_request_handler<M, E, H, F>(&self, handler: H)
478    where
479        M: EnvelopedMessage + RequestMessage + EntityMessage,
480        E: 'static,
481        H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
482        F: 'static + Future<Output = Result<M::Response>>,
483    {
484        let message_type_id = TypeId::of::<M>();
485        let entity_type_id = TypeId::of::<E>();
486        let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
487            (envelope as &dyn Any)
488                .downcast_ref::<TypedEnvelope<M>>()
489                .unwrap()
490                .payload
491                .remote_entity_id()
492        };
493        self.0
494            .client
495            .message_handler_set()
496            .lock()
497            .add_entity_message_handler(
498                message_type_id,
499                entity_type_id,
500                entity_id_extractor,
501                Arc::new(move |entity, envelope, client, cx| {
502                    let entity = entity.downcast::<E>().unwrap();
503                    let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
504                    let request_id = envelope.message_id();
505                    handler(entity, *envelope, cx)
506                        .then(move |result| async move {
507                            match result {
508                                Ok(response) => {
509                                    client.send_response(request_id, response)?;
510                                    Ok(())
511                                }
512                                Err(error) => {
513                                    client.send_response(request_id, error.to_proto())?;
514                                    Err(error)
515                                }
516                            }
517                        })
518                        .boxed_local()
519                }),
520            );
521    }
522
523    pub fn add_entity_stream_request_handler<M, E, H, F, S>(&self, handler: H)
524    where
525        M: EnvelopedMessage + RequestMessage + EntityMessage,
526        E: 'static,
527        H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
528        F: 'static + Future<Output = Result<S>>,
529        S: 'static + Stream<Item = Result<M::Response>>,
530    {
531        let message_type_id = TypeId::of::<M>();
532        let entity_type_id = TypeId::of::<E>();
533        let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
534            (envelope as &dyn Any)
535                .downcast_ref::<TypedEnvelope<M>>()
536                .unwrap()
537                .payload
538                .remote_entity_id()
539        };
540        self.0
541            .client
542            .message_handler_set()
543            .lock()
544            .add_entity_message_handler(
545                message_type_id,
546                entity_type_id,
547                entity_id_extractor,
548                Arc::new(move |entity, envelope, client, cx| {
549                    let entity = entity.downcast::<E>().unwrap();
550                    let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
551                    let request_id = envelope.message_id();
552                    let stream = handler(entity, *envelope, cx);
553                    async move {
554                        // An Error response is itself a terminal stream frame on
555                        // both transports (Peer and ChannelClient), so we don't
556                        // need to follow it with an EndStream.
557                        match stream.await {
558                            Ok(stream) => {
559                                futures::pin_mut!(stream);
560                                while let Some(result) = stream.next().await {
561                                    match result {
562                                        Ok(response) => {
563                                            client.send_response(request_id, response)?
564                                        }
565                                        Err(error) => {
566                                            client.send_response(request_id, error.to_proto())?;
567                                            return Err(error);
568                                        }
569                                    }
570                                }
571                                client.send_response(request_id, proto::EndStream {})?;
572                                Ok(())
573                            }
574                            Err(error) => {
575                                client.send_response(request_id, error.to_proto())?;
576                                Err(error)
577                            }
578                        }
579                    }
580                    .boxed_local()
581                }),
582            );
583    }
584
585    pub fn add_entity_message_handler<M, E, H, F>(&self, handler: H)
586    where
587        M: EnvelopedMessage + EntityMessage,
588        E: 'static,
589        H: 'static + Sync + Send + Fn(gpui::Entity<E>, TypedEnvelope<M>, AsyncApp) -> F,
590        F: 'static + Future<Output = Result<()>>,
591    {
592        let message_type_id = TypeId::of::<M>();
593        let entity_type_id = TypeId::of::<E>();
594        let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
595            (envelope as &dyn Any)
596                .downcast_ref::<TypedEnvelope<M>>()
597                .unwrap()
598                .payload
599                .remote_entity_id()
600        };
601        self.0
602            .client
603            .message_handler_set()
604            .lock()
605            .add_entity_message_handler(
606                message_type_id,
607                entity_type_id,
608                entity_id_extractor,
609                Arc::new(move |entity, envelope, _, cx| {
610                    let entity = entity.downcast::<E>().unwrap();
611                    let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
612                    handler(entity, *envelope, cx).boxed_local()
613                }),
614            );
615    }
616
617    pub fn subscribe_to_entity<E: 'static>(&self, remote_id: u64, entity: &Entity<E>) {
618        let id = (TypeId::of::<E>(), remote_id);
619
620        let mut message_handlers = self.0.client.message_handler_set().lock();
621        if message_handlers
622            .entities_by_type_and_remote_id
623            .contains_key(&id)
624        {
625            panic!("already subscribed to entity");
626        }
627
628        message_handlers.entities_by_type_and_remote_id.insert(
629            id,
630            EntityMessageSubscriber::Entity {
631                handle: entity.downgrade().into(),
632            },
633        );
634    }
635
636    pub fn has_wsl_interop(&self) -> bool {
637        self.0.client.has_wsl_interop()
638    }
639}
640
641fn to_any_envelope<T: EnvelopedMessage>(
642    envelope: &TypedEnvelope<proto::LspQueryResponse>,
643    response: T,
644) -> Box<dyn AnyTypedEnvelope> {
645    Box::new(proto::TypedEnvelope {
646        sender_id: envelope.sender_id,
647        original_sender_id: envelope.original_sender_id,
648        message_id: envelope.message_id,
649        received_at: envelope.received_at,
650        payload: response,
651    }) as Box<_>
652}
653
654#[cfg(any(test, feature = "test-support"))]
655pub struct NoopProtoClient {
656    handler_set: parking_lot::Mutex<ProtoMessageHandlerSet>,
657}
658
659#[cfg(any(test, feature = "test-support"))]
660impl NoopProtoClient {
661    pub fn new() -> Arc<Self> {
662        Arc::new(Self {
663            handler_set: parking_lot::Mutex::new(ProtoMessageHandlerSet::default()),
664        })
665    }
666}
667
668#[cfg(any(test, feature = "test-support"))]
669impl ProtoClient for NoopProtoClient {
670    fn request(
671        &self,
672        _: proto::Envelope,
673        _: &'static str,
674    ) -> futures::future::BoxFuture<'static, Result<proto::Envelope>> {
675        unimplemented!()
676    }
677    fn send(&self, _: proto::Envelope, _: &'static str) -> Result<()> {
678        Ok(())
679    }
680    fn send_response(&self, _: proto::Envelope, _: &'static str) -> Result<()> {
681        Ok(())
682    }
683    fn message_handler_set(&self) -> &parking_lot::Mutex<ProtoMessageHandlerSet> {
684        &self.handler_set
685    }
686    fn is_via_collab(&self) -> bool {
687        false
688    }
689    fn has_wsl_interop(&self) -> bool {
690        false
691    }
692}
693
Served at tenant.openagents/omega Member data and write actions are omitted.