1use std::collections::HashMap;
25use std::pin::Pin;
26use std::sync::{Arc, LazyLock, OnceLock, Weak};
27
28use async_trait::async_trait;
29use base64::Engine;
30use bytes::Bytes;
31use futures_util::{SinkExt, Stream, StreamExt};
32use http::HeaderMap;
33use http::header::{HeaderName, HeaderValue};
34use parking_lot::Mutex;
35use tokio::net::TcpStream;
36use tokio::sync::{Mutex as AsyncMutex, mpsc};
37use tokio_tungstenite::tungstenite::Message;
38use tokio_tungstenite::tungstenite::client::IntoClientRequest;
39use tokio_tungstenite::{MaybeTlsStream, WebSocketStream, connect_async};
40use tokio_util::sync::CancellationToken;
41use tracing::warn;
42
43use self::http_response_reader::HttpResponseReader;
44use crate::generated::api_types::{
45 LlmInferenceHttpRequestChunkRequest, LlmInferenceHttpRequestStartRequest,
46 LlmInferenceHttpRequestStartTransport, LlmInferenceHttpResponseChunkError,
47 LlmInferenceHttpResponseChunkRequest, LlmInferenceHttpResponseStartRequest,
48};
49use crate::{
50 Client, ClientInner, JsonRpcRequest, JsonRpcResponse, RequestId, SessionId, error_codes,
51};
52
53mod http_response_reader;
54
55const METHOD_HTTP_REQUEST_START: &str = "llmInference.httpRequestStart";
56const METHOD_HTTP_REQUEST_CHUNK: &str = "llmInference.httpRequestChunk";
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum CopilotRequestTransport {
61 #[default]
63 Http,
64 WebSocket,
67}
68
69impl CopilotRequestTransport {
70 fn from_wire(value: Option<LlmInferenceHttpRequestStartTransport>) -> Self {
71 match value {
72 Some(LlmInferenceHttpRequestStartTransport::Websocket) => Self::WebSocket,
73 _ => Self::Http,
74 }
75 }
76}
77
78#[derive(Debug)]
80#[non_exhaustive]
81pub enum CopilotRequestError {
82 ConnectionClosed,
84
85 InvalidState(String),
88
89 Upstream(String),
91
92 Handler(String),
94
95 Rpc(crate::Error),
97}
98
99impl CopilotRequestError {
100 pub fn message(message: impl Into<String>) -> Self {
103 Self::Handler(message.into())
104 }
105}
106
107impl std::fmt::Display for CopilotRequestError {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match self {
110 Self::ConnectionClosed => {
111 f.write_str("Copilot request response used after RPC connection closed")
112 }
113 Self::InvalidState(message) | Self::Upstream(message) | Self::Handler(message) => {
114 f.write_str(message)
115 }
116 Self::Rpc(err) => write!(f, "{err}"),
117 }
118 }
119}
120
121impl std::error::Error for CopilotRequestError {
122 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
123 match self {
124 Self::Rpc(err) => Some(err),
125 _ => None,
126 }
127 }
128}
129
130impl From<crate::Error> for CopilotRequestError {
131 fn from(err: crate::Error) -> Self {
132 Self::Rpc(err)
133 }
134}
135
136#[derive(Clone)]
139#[non_exhaustive]
140pub struct CopilotRequestContext {
141 pub request_id: String,
143 pub session_id: Option<String>,
146 pub agent_id: Option<String>,
148 pub parent_agent_id: Option<String>,
150 pub interaction_type: Option<String>,
152 pub transport: CopilotRequestTransport,
154 pub url: String,
156 pub headers: HeaderMap,
158 pub cancel: CancellationToken,
160}
161
162pub type CopilotHttpResponseBody =
172 Pin<Box<dyn Stream<Item = Result<Bytes, CopilotRequestError>> + Send>>;
173
174#[non_exhaustive]
176pub struct CopilotHttpRequest {
177 pub method: String,
179 pub url: String,
181 pub headers: HeaderMap,
183 pub body: Vec<u8>,
185 pub cancel: CancellationToken,
187}
188
189#[non_exhaustive]
191pub struct CopilotHttpResponse {
192 pub status: u16,
194 pub status_text: Option<String>,
196 pub headers: HeaderMap,
198 pub body: CopilotHttpResponseBody,
200}
201
202impl CopilotHttpResponse {
203 pub fn new(
205 status: u16,
206 status_text: Option<String>,
207 headers: HeaderMap,
208 body: CopilotHttpResponseBody,
209 ) -> Self {
210 Self {
211 status,
212 status_text,
213 headers,
214 body,
215 }
216 }
217}
218
219#[derive(Clone)]
221pub struct CopilotWebSocketMessage {
222 pub data: Vec<u8>,
224 pub binary: bool,
226}
227
228impl CopilotWebSocketMessage {
229 pub fn from_text(data: impl Into<String>) -> Self {
232 Self {
233 data: data.into().into_bytes(),
234 binary: false,
235 }
236 }
237}
238
239#[derive(Clone)]
242pub struct CopilotWebSocketResponse {
243 exchange: Arc<CopilotRequestExchange>,
244}
245
246impl CopilotWebSocketResponse {
247 fn new(exchange: Arc<CopilotRequestExchange>) -> Self {
248 Self { exchange }
249 }
250
251 pub async fn send_message(
253 &self,
254 message: CopilotWebSocketMessage,
255 ) -> Result<(), CopilotRequestError> {
256 self.exchange.ensure_ws_started().await?;
257 if message.binary {
258 self.exchange.write_binary(&message.data).await
259 } else {
260 let text = String::from_utf8_lossy(&message.data);
261 self.exchange.write_text(&text).await
262 }
263 }
264
265 pub async fn close(&self) -> Result<(), CopilotRequestError> {
267 self.exchange.end_response().await
268 }
269
270 async fn fail(
271 &self,
272 message: impl Into<String>,
273 code: Option<String>,
274 ) -> Result<(), CopilotRequestError> {
275 self.exchange.error_response(message, code).await
276 }
277}
278
279#[async_trait]
283pub trait CopilotWebSocketHandler: Send + Sync {
284 async fn send_request_message(
286 &self,
287 message: CopilotWebSocketMessage,
288 ) -> Result<(), CopilotRequestError>;
289
290 async fn close(&self) -> Result<(), CopilotRequestError>;
292}
293
294#[async_trait]
300pub trait CopilotRequestHandler: Send + Sync + 'static {
301 async fn send_request(
305 &self,
306 request: CopilotHttpRequest,
307 _ctx: &CopilotRequestContext,
308 ) -> Result<CopilotHttpResponse, CopilotRequestError> {
309 forward_http(request).await
310 }
311
312 async fn open_websocket(
324 &self,
325 ctx: &CopilotRequestContext,
326 response: CopilotWebSocketResponse,
327 ) -> Result<Box<dyn CopilotWebSocketHandler>, CopilotRequestError> {
328 let handler = CopilotWebSocketForwarder::builder(ctx.url.clone(), ctx.headers.clone())
329 .connect(response)
330 .await?;
331 Ok(Box::new(handler))
332 }
333}
334
335#[async_trait]
338impl<H: CopilotRequestHandler> CopilotRequestHandler for Arc<H> {
339 async fn send_request(
340 &self,
341 request: CopilotHttpRequest,
342 ctx: &CopilotRequestContext,
343 ) -> Result<CopilotHttpResponse, CopilotRequestError> {
344 (**self).send_request(request, ctx).await
345 }
346
347 async fn open_websocket(
348 &self,
349 ctx: &CopilotRequestContext,
350 response: CopilotWebSocketResponse,
351 ) -> Result<Box<dyn CopilotWebSocketHandler>, CopilotRequestError> {
352 (**self).open_websocket(ctx, response).await
353 }
354}
355const FORBIDDEN_HEADERS: &[&str] = &[
357 "host",
358 "connection",
359 "content-length",
360 "transfer-encoding",
361 "keep-alive",
362 "upgrade",
363 "proxy-connection",
364 "te",
365 "trailer",
366];
367
368fn is_forbidden_header(name: &HeaderName) -> bool {
369 let name = name.as_str();
370 FORBIDDEN_HEADERS.contains(&name) || name.starts_with("sec-websocket")
371}
372
373fn strip_forbidden_headers(headers: &mut HeaderMap) {
375 let forbidden: Vec<HeaderName> = headers
376 .keys()
377 .filter(|name| is_forbidden_header(name))
378 .cloned()
379 .collect();
380 for name in forbidden {
381 headers.remove(&name);
382 }
383}
384
385static SHARED_HTTP_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
386 reqwest::Client::builder()
387 .redirect(reqwest::redirect::Policy::none())
388 .build()
389 .expect("default reqwest client must build")
390});
391
392pub async fn forward_http(
397 request: CopilotHttpRequest,
398) -> Result<CopilotHttpResponse, CopilotRequestError> {
399 let method = reqwest::Method::from_bytes(request.method.as_bytes())
400 .map_err(|e| CopilotRequestError::InvalidState(format!("invalid HTTP method: {e}")))?;
401
402 let mut headers = request.headers;
403 strip_forbidden_headers(&mut headers);
404
405 let mut builder = SHARED_HTTP_CLIENT
406 .request(method, &request.url)
407 .headers(headers);
408 if !request.body.is_empty() {
409 builder = builder.body(request.body);
410 }
411
412 let response = tokio::select! {
413 _ = request.cancel.cancelled() => {
414 return Err(CopilotRequestError::message("Request cancelled by runtime"));
415 }
416 result = builder.send() => result.map_err(|e| CopilotRequestError::Upstream(e.to_string()))?,
417 };
418
419 let status = response.status().as_u16();
420 let status_text = response.status().canonical_reason().map(str::to_string);
421 let headers = response.headers().clone();
422 let body = response
423 .bytes_stream()
424 .map(|item| item.map_err(|e| CopilotRequestError::Upstream(e.to_string())));
425
426 Ok(CopilotHttpResponse {
427 status,
428 status_text,
429 headers,
430 body: Box::pin(body),
431 })
432}
433
434type UpstreamWrite =
435 futures_util::stream::SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>;
436
437pub type WebSocketTransform =
439 Arc<dyn Fn(CopilotWebSocketMessage) -> Option<CopilotWebSocketMessage> + Send + Sync>;
440
441pub struct CopilotWebSocketForwarderBuilder {
443 url: String,
444 headers: HeaderMap,
445 on_send_request_message: Option<WebSocketTransform>,
446 on_send_response_message: Option<WebSocketTransform>,
447}
448
449impl CopilotWebSocketForwarderBuilder {
450 pub fn on_send_request_message(mut self, transform: WebSocketTransform) -> Self {
452 self.on_send_request_message = Some(transform);
453 self
454 }
455
456 pub fn on_send_response_message(mut self, transform: WebSocketTransform) -> Self {
458 self.on_send_response_message = Some(transform);
459 self
460 }
461
462 pub async fn connect(
465 self,
466 response: CopilotWebSocketResponse,
467 ) -> Result<CopilotWebSocketForwarder, CopilotRequestError> {
468 let mut request =
469 self.url.as_str().into_client_request().map_err(|e| {
470 CopilotRequestError::Upstream(format!("invalid websocket url: {e}"))
471 })?;
472 for (name, value) in &self.headers {
473 if is_forbidden_header(name) {
474 continue;
475 }
476 request.headers_mut().append(name.clone(), value.clone());
477 }
478
479 let (stream, _) = connect_async(request)
480 .await
481 .map_err(|e| CopilotRequestError::Upstream(format!("websocket connect failed: {e}")))?;
482 let (write, mut read) = stream.split();
483
484 let cancel = CancellationToken::new();
485 let loop_cancel = cancel.clone();
486 let on_response = self.on_send_response_message.clone();
487 tokio::spawn(async move {
488 loop {
489 tokio::select! {
490 _ = loop_cancel.cancelled() => break,
491 msg = read.next() => match msg {
492 Some(Ok(Message::Text(text))) => {
493 let message = CopilotWebSocketMessage::from_text(text);
494 if let Some(out) = apply_transform(&on_response, message) {
495 let _ = response.send_message(out).await;
496 }
497 }
498 Some(Ok(Message::Binary(data))) => {
499 let message = CopilotWebSocketMessage { data, binary: true };
500 if let Some(out) = apply_transform(&on_response, message) {
501 let _ = response.send_message(out).await;
502 }
503 }
504 Some(Ok(Message::Close(_))) | None => break,
505 Some(Ok(_)) => continue,
506 Some(Err(e)) => {
507 let _ = response.fail(e.to_string(), None).await;
508 return;
509 }
510 }
511 }
512 }
513 let _ = response.close().await;
514 });
515
516 Ok(CopilotWebSocketForwarder {
517 write: AsyncMutex::new(Some(write)),
518 on_send_request_message: self.on_send_request_message,
519 cancel,
520 })
521 }
522}
523
524pub struct CopilotWebSocketForwarder {
528 write: AsyncMutex<Option<UpstreamWrite>>,
529 on_send_request_message: Option<WebSocketTransform>,
530 cancel: CancellationToken,
531}
532
533impl CopilotWebSocketForwarder {
534 pub fn builder(url: String, headers: HeaderMap) -> CopilotWebSocketForwarderBuilder {
537 CopilotWebSocketForwarderBuilder {
538 url,
539 headers,
540 on_send_request_message: None,
541 on_send_response_message: None,
542 }
543 }
544}
545
546#[async_trait]
547impl CopilotWebSocketHandler for CopilotWebSocketForwarder {
548 async fn send_request_message(
549 &self,
550 message: CopilotWebSocketMessage,
551 ) -> Result<(), CopilotRequestError> {
552 let Some(message) = apply_transform(&self.on_send_request_message, message) else {
553 return Ok(());
554 };
555 let ws_message = if message.binary {
556 Message::Binary(message.data)
557 } else {
558 let text = match String::from_utf8(message.data) {
559 Ok(text) => text,
560 Err(err) => String::from_utf8_lossy(err.as_bytes()).into_owned(),
561 };
562 Message::Text(text)
563 };
564 let mut guard = self.write.lock().await;
565 if let Some(write) = guard.as_mut() {
566 write
567 .send(ws_message)
568 .await
569 .map_err(|e| CopilotRequestError::Upstream(e.to_string()))?;
570 }
571 Ok(())
572 }
573
574 async fn close(&self) -> Result<(), CopilotRequestError> {
575 self.cancel.cancel();
576 let mut guard = self.write.lock().await;
577 if let Some(mut write) = guard.take() {
578 let _ = write.send(Message::Close(None)).await;
579 let _ = write.close().await;
580 }
581 Ok(())
582 }
583}
584
585fn apply_transform(
586 transform: &Option<WebSocketTransform>,
587 message: CopilotWebSocketMessage,
588) -> Option<CopilotWebSocketMessage> {
589 match transform {
590 Some(f) => f(message),
591 None => Some(message),
592 }
593}
594
595#[derive(Default)]
597struct ResponseState {
598 started: bool,
599 finished: bool,
600}
601
602#[derive(Default)]
612struct RequestMeta {
613 session_id: Option<String>,
614 agent_id: Option<String>,
615 parent_agent_id: Option<String>,
616 interaction_type: Option<String>,
617 method: String,
618 url: String,
619 headers: HeaderMap,
620 transport: CopilotRequestTransport,
621}
622
623struct CopilotRequestExchange {
624 request_id: String,
625 meta: OnceLock<RequestMeta>,
626 cancel: CancellationToken,
627 client: Weak<ClientInner>,
628 body_tx: Mutex<Option<mpsc::UnboundedSender<Vec<u8>>>>,
631 body_rx: AsyncMutex<mpsc::UnboundedReceiver<Vec<u8>>>,
632 state: Mutex<ResponseState>,
633}
634
635impl CopilotRequestExchange {
636 fn new(request_id: String, client: Weak<ClientInner>) -> Self {
637 let (body_tx, body_rx) = mpsc::unbounded_channel();
638 Self {
639 request_id,
640 meta: OnceLock::new(),
641 cancel: CancellationToken::new(),
642 client,
643 body_tx: Mutex::new(Some(body_tx)),
644 body_rx: AsyncMutex::new(body_rx),
645 state: Mutex::new(ResponseState::default()),
646 }
647 }
648
649 fn set_context(&self, params: LlmInferenceHttpRequestStartRequest) {
651 let _ = self.meta.set(RequestMeta {
652 session_id: params.session_id.map(SessionId::into_inner),
653 agent_id: params.agent_id,
654 parent_agent_id: params.parent_agent_id,
655 interaction_type: params.interaction_type,
656 method: params.method,
657 url: params.url,
658 headers: headers_from_wire(¶ms.headers),
659 transport: CopilotRequestTransport::from_wire(params.transport),
660 });
661 }
662
663 fn meta(&self) -> &RequestMeta {
667 self.meta.get_or_init(RequestMeta::default)
668 }
669
670 fn context(&self) -> CopilotRequestContext {
671 let meta = self.meta();
672 CopilotRequestContext {
673 request_id: self.request_id.clone(),
674 session_id: meta.session_id.clone(),
675 agent_id: meta.agent_id.clone(),
676 parent_agent_id: meta.parent_agent_id.clone(),
677 interaction_type: meta.interaction_type.clone(),
678 transport: meta.transport,
679 url: meta.url.clone(),
680 headers: meta.headers.clone(),
681 cancel: self.cancel.clone(),
682 }
683 }
684
685 fn client(&self) -> Result<Client, CopilotRequestError> {
686 self.client
687 .upgrade()
688 .map(Client::from_inner)
689 .ok_or(CopilotRequestError::ConnectionClosed)
690 }
691
692 fn request_id(&self) -> RequestId {
693 RequestId::new(self.request_id.clone())
694 }
695
696 fn push_chunk(&self, data: Vec<u8>) {
699 if let Some(tx) = self.body_tx.lock().as_ref() {
700 let _ = tx.send(data);
701 }
702 }
703
704 fn push_end(&self) {
705 *self.body_tx.lock() = None;
706 }
707
708 fn push_cancel(&self) {
709 self.cancel.cancel();
710 *self.body_tx.lock() = None;
711 }
712
713 async fn recv_body(&self) -> Option<Vec<u8>> {
714 self.body_rx.lock().await.recv().await
715 }
716
717 async fn drain_body(&self) -> Vec<u8> {
718 let mut buf = Vec::new();
719 let mut rx = self.body_rx.lock().await;
720 while let Some(frame) = rx.recv().await {
721 buf.extend_from_slice(&frame);
722 }
723 buf
724 }
725
726 fn started(&self) -> bool {
731 self.state.lock().started
732 }
733
734 fn finished(&self) -> bool {
735 self.state.lock().finished
736 }
737
738 async fn start_response(
739 &self,
740 status: u16,
741 status_text: Option<String>,
742 headers: HeaderMap,
743 ) -> Result<(), CopilotRequestError> {
744 {
745 let mut state = self.state.lock();
746 if state.started {
747 return Err(CopilotRequestError::InvalidState(
748 "response start() called twice".to_string(),
749 ));
750 }
751 if state.finished {
752 return Err(CopilotRequestError::InvalidState(
753 "response already finished".to_string(),
754 ));
755 }
756 state.started = true;
757 }
758 let request = LlmInferenceHttpResponseStartRequest {
759 headers: headers_to_wire(&headers),
760 request_id: self.request_id(),
761 status: i64::from(status),
762 status_text,
763 };
764 self.client()?
765 .rpc()
766 .llm_inference()
767 .http_response_start(request)
768 .await?;
769 Ok(())
770 }
771
772 async fn ensure_ws_started(&self) -> Result<(), CopilotRequestError> {
776 if self.started() {
777 return Ok(());
778 }
779 self.start_response(101, None, HeaderMap::new()).await
780 }
781
782 async fn write_text(&self, text: &str) -> Result<(), CopilotRequestError> {
783 self.write(text.to_string(), false).await
784 }
785
786 async fn write_binary(&self, data: &[u8]) -> Result<(), CopilotRequestError> {
787 let encoded = base64::engine::general_purpose::STANDARD.encode(data);
788 self.write(encoded, true).await
789 }
790
791 async fn write(&self, data: String, binary: bool) -> Result<(), CopilotRequestError> {
792 {
793 let state = self.state.lock();
794 if !state.started {
795 return Err(CopilotRequestError::InvalidState(
796 "response write called before start()".to_string(),
797 ));
798 }
799 if state.finished {
800 return Err(CopilotRequestError::InvalidState(
801 "response write called after end()/error()".to_string(),
802 ));
803 }
804 }
805 let request = LlmInferenceHttpResponseChunkRequest {
806 binary: binary.then_some(true),
807 data,
808 end: Some(false),
809 error: None,
810 request_id: self.request_id(),
811 };
812 self.client()?
813 .rpc()
814 .llm_inference()
815 .http_response_chunk(request)
816 .await?;
817 Ok(())
818 }
819
820 async fn end_response(&self) -> Result<(), CopilotRequestError> {
821 {
822 let mut state = self.state.lock();
823 if state.finished {
824 return Ok(());
825 }
826 state.finished = true;
827 }
828 let request = LlmInferenceHttpResponseChunkRequest {
829 binary: None,
830 data: String::new(),
831 end: Some(true),
832 error: None,
833 request_id: self.request_id(),
834 };
835 self.client()?
836 .rpc()
837 .llm_inference()
838 .http_response_chunk(request)
839 .await?;
840 Ok(())
841 }
842
843 async fn error_response(
844 &self,
845 message: impl Into<String>,
846 code: Option<String>,
847 ) -> Result<(), CopilotRequestError> {
848 {
849 let mut state = self.state.lock();
850 if state.finished {
851 return Ok(());
852 }
853 state.finished = true;
854 }
855 let request = LlmInferenceHttpResponseChunkRequest {
856 binary: None,
857 data: String::new(),
858 end: Some(true),
859 error: Some(LlmInferenceHttpResponseChunkError {
860 code,
861 message: message.into(),
862 }),
863 request_id: self.request_id(),
864 };
865 self.client()?
866 .rpc()
867 .llm_inference()
868 .http_response_chunk(request)
869 .await?;
870 Ok(())
871 }
872}
873
874async fn drive_exchange(
876 exchange: &Arc<CopilotRequestExchange>,
877 handler: &Arc<dyn CopilotRequestHandler>,
878) -> Result<(), CopilotRequestError> {
879 let ctx = exchange.context();
880 let meta = exchange.meta();
881 match meta.transport {
882 CopilotRequestTransport::Http => {
883 let body = exchange.drain_body().await;
884 let request = CopilotHttpRequest {
885 method: meta.method.clone(),
886 url: meta.url.clone(),
887 headers: meta.headers.clone(),
888 body,
889 cancel: ctx.cancel.clone(),
890 };
891 let response = handler.send_request(request, &ctx).await?;
892 stream_http_response(response, exchange, &ctx.cancel).await
893 }
894 CopilotRequestTransport::WebSocket => {
895 exchange.ensure_ws_started().await?;
902 let response = CopilotWebSocketResponse::new(exchange.clone());
903 let ws = handler.open_websocket(&ctx, response).await?;
904 let result = pump_websocket_requests(ws.as_ref(), exchange, &ctx.cancel).await;
905 let _ = ws.close().await;
906 match result {
907 Ok(()) => exchange.end_response().await,
908 Err(err) if ctx.cancel.is_cancelled() => {
909 exchange
910 .error_response(
911 "Request cancelled by runtime",
912 Some("cancelled".to_string()),
913 )
914 .await?;
915 let _ = err;
916 Ok(())
917 }
918 Err(err) => Err(err),
919 }
920 }
921 }
922}
923
924async fn stream_http_response(
926 response: CopilotHttpResponse,
927 exchange: &CopilotRequestExchange,
928 cancel: &CancellationToken,
929) -> Result<(), CopilotRequestError> {
930 tokio::select! {
931 biased;
932 result = exchange.start_response(response.status, response.status_text, response.headers) => {
936 result?;
937 }
938 _ = cancel.cancelled() => {
939 drop(response.body);
940 return exchange
941 .error_response("Request cancelled by runtime", Some("cancelled".to_string()))
942 .await;
943 }
944 }
945
946 let forward = async {
947 let mut reader = HttpResponseReader::new(response.body);
948 let mut chunk = Vec::new();
949 loop {
950 match reader.next_chunk(&mut chunk).await {
951 Ok(true) => {}
952 Ok(false) => return exchange.end_response().await,
953 Err(error) => return exchange.error_response(error.to_string(), None).await,
954 }
955
956 let write = exchange.write_binary(&chunk);
959 tokio::pin!(write);
960 loop {
961 tokio::select! {
962 biased;
963 result = &mut write => {
964 result?;
965 break;
966 }
967 () = reader.read_more(), if reader.can_read() => {}
968 }
969 }
970 }
971 };
972 tokio::select! {
973 biased;
974 _ = cancel.cancelled() => {
975 exchange
976 .error_response("Request cancelled by runtime", Some("cancelled".to_string()))
977 .await
978 }
979 result = forward => result,
980 }
981}
982
983async fn pump_websocket_requests(
986 handler: &dyn CopilotWebSocketHandler,
987 exchange: &CopilotRequestExchange,
988 cancel: &CancellationToken,
989) -> Result<(), CopilotRequestError> {
990 loop {
991 tokio::select! {
992 _ = cancel.cancelled() => {
993 return Err(CopilotRequestError::message("Request cancelled by runtime"));
994 }
995 frame = exchange.recv_body() => match frame {
996 Some(data) => {
997 handler
998 .send_request_message(CopilotWebSocketMessage { data, binary: false })
999 .await?;
1000 }
1001 None => return Ok(()),
1002 }
1003 }
1004 }
1005}
1006
1007async fn finalize_exchange(
1010 exchange: &CopilotRequestExchange,
1011 result: Result<(), CopilotRequestError>,
1012) {
1013 match result {
1014 Ok(()) => {
1015 if !exchange.finished() {
1016 fail_via_response(
1017 exchange,
1018 502,
1019 "Copilot request handler returned without finalising the response".to_string(),
1020 )
1021 .await;
1022 }
1023 }
1024 Err(err) => {
1025 if exchange.finished() {
1026 return;
1027 }
1028 if exchange.cancel.is_cancelled() {
1029 if !exchange.started() {
1030 let _ = exchange.start_response(499, None, HeaderMap::new()).await;
1031 }
1032 let _ = exchange
1033 .error_response(
1034 "Request cancelled by runtime",
1035 Some("cancelled".to_string()),
1036 )
1037 .await;
1038 } else {
1039 fail_via_response(exchange, 502, err.to_string()).await;
1040 }
1041 }
1042 }
1043}
1044
1045async fn fail_via_response(exchange: &CopilotRequestExchange, status: u16, message: String) {
1046 if !exchange.started() {
1047 let _ = exchange
1048 .start_response(status, None, HeaderMap::new())
1049 .await;
1050 }
1051 let _ = exchange.error_response(message, None).await;
1052}
1053
1054pub(crate) struct CopilotRequestDispatcher {
1057 handler: Arc<dyn CopilotRequestHandler>,
1058 client: OnceLock<Weak<ClientInner>>,
1059 pending: Mutex<HashMap<String, Arc<CopilotRequestExchange>>>,
1060}
1061
1062impl CopilotRequestDispatcher {
1063 pub(crate) fn new(handler: Arc<dyn CopilotRequestHandler>) -> Self {
1064 Self {
1065 handler,
1066 client: OnceLock::new(),
1067 pending: Mutex::new(HashMap::new()),
1068 }
1069 }
1070
1071 pub(crate) fn set_client(&self, client: Weak<ClientInner>) {
1072 let _ = self.client.set(client);
1073 }
1074
1075 fn client(&self) -> Option<Client> {
1076 self.client
1077 .get()
1078 .and_then(Weak::upgrade)
1079 .map(Client::from_inner)
1080 }
1081
1082 fn client_weak(&self) -> Weak<ClientInner> {
1083 self.client.get().cloned().unwrap_or_else(Weak::new)
1084 }
1085
1086 pub(crate) async fn dispatch(self: &Arc<Self>, request: JsonRpcRequest) {
1087 match request.method.as_str() {
1088 METHOD_HTTP_REQUEST_START => self.handle_start(request).await,
1089 METHOD_HTTP_REQUEST_CHUNK => self.handle_chunk(request).await,
1090 other => {
1091 warn!(method = other, "unknown llmInference request method");
1092 self.send_error(request.id, "unknown llmInference method")
1093 .await;
1094 }
1095 }
1096 }
1097
1098 fn get_or_create_exchange(&self, request_id: String) -> Arc<CopilotRequestExchange> {
1099 self.pending
1105 .lock()
1106 .entry(request_id.clone())
1107 .or_insert_with(|| {
1108 Arc::new(CopilotRequestExchange::new(request_id, self.client_weak()))
1109 })
1110 .clone()
1111 }
1112
1113 async fn handle_start(self: &Arc<Self>, request: JsonRpcRequest) {
1114 let id = request.id;
1115 let Some(params) = parse_params::<LlmInferenceHttpRequestStartRequest>(&request) else {
1116 self.send_error(id, "invalid llmInference.httpRequestStart params")
1117 .await;
1118 return;
1119 };
1120
1121 let request_id = params.request_id.clone().into_inner();
1124 let exchange = self.get_or_create_exchange(request_id.clone());
1125 exchange.set_context(params);
1126
1127 let handler = self.handler.clone();
1128 let dispatcher = Arc::clone(self);
1129 let exchange_for_task = exchange.clone();
1130 tokio::spawn(async move {
1131 let result = drive_exchange(&exchange_for_task, &handler).await;
1132 finalize_exchange(&exchange_for_task, result).await;
1133 dispatcher.remove_pending(&request_id);
1134 });
1135
1136 self.ack(id).await;
1137 }
1138
1139 async fn handle_chunk(&self, request: JsonRpcRequest) {
1140 let id = request.id;
1141 let Some(params) = parse_params::<LlmInferenceHttpRequestChunkRequest>(&request) else {
1142 self.send_error(id, "invalid llmInference.httpRequestChunk params")
1143 .await;
1144 return;
1145 };
1146
1147 let exchange = self.get_or_create_exchange(params.request_id.to_string());
1150 apply_chunk(&exchange, ¶ms);
1151
1152 self.ack(id).await;
1153 }
1154
1155 fn remove_pending(&self, request_id: &str) {
1156 self.pending.lock().remove(request_id);
1157 }
1158
1159 async fn ack(&self, id: u64) {
1160 let Some(client) = self.client() else {
1161 return;
1162 };
1163 let _ = client
1164 .send_response(&JsonRpcResponse {
1165 jsonrpc: "2.0".to_string(),
1166 id,
1167 result: Some(serde_json::json!({})),
1168 error: None,
1169 })
1170 .await;
1171 }
1172
1173 async fn send_error(&self, id: u64, message: &str) {
1174 let Some(client) = self.client() else {
1175 return;
1176 };
1177 let _ = client
1178 .send_response(&JsonRpcResponse {
1179 jsonrpc: "2.0".to_string(),
1180 id,
1181 result: None,
1182 error: Some(crate::JsonRpcError {
1183 code: error_codes::INTERNAL_ERROR,
1184 message: message.to_string(),
1185 data: None,
1186 }),
1187 })
1188 .await;
1189 }
1190}
1191
1192fn apply_chunk(exchange: &CopilotRequestExchange, params: &LlmInferenceHttpRequestChunkRequest) {
1195 if params.cancel == Some(true) {
1196 exchange.push_cancel();
1197 return;
1198 }
1199
1200 if !params.data.is_empty() {
1201 let decoded = if params.binary == Some(true) {
1202 match base64::engine::general_purpose::STANDARD.decode(params.data.as_bytes()) {
1203 Ok(bytes) => bytes,
1204 Err(e) => {
1205 warn!(error = %e, "failed to decode base64 llmInference body chunk");
1206 return;
1207 }
1208 }
1209 } else {
1210 params.data.clone().into_bytes()
1211 };
1212 exchange.push_chunk(decoded);
1213 }
1214
1215 if params.end == Some(true) {
1216 exchange.push_end();
1217 }
1218}
1219
1220fn parse_params<T: serde::de::DeserializeOwned>(request: &JsonRpcRequest) -> Option<T> {
1221 request
1222 .params
1223 .as_ref()
1224 .and_then(|p| serde_json::from_value(p.clone()).ok())
1225}
1226
1227fn headers_from_wire(wire: &HashMap<String, Vec<String>>) -> HeaderMap {
1230 let mut headers = HeaderMap::new();
1231 for (name, values) in wire {
1232 let Ok(header_name) = HeaderName::from_bytes(name.as_bytes()) else {
1233 continue;
1234 };
1235 for value in values {
1236 let Ok(header_value) = HeaderValue::from_str(value) else {
1237 continue;
1238 };
1239 headers.append(header_name.clone(), header_value);
1240 }
1241 }
1242 headers
1243}
1244
1245fn headers_to_wire(headers: &HeaderMap) -> HashMap<String, Vec<String>> {
1248 let mut wire: HashMap<String, Vec<String>> = HashMap::new();
1249 for (name, value) in headers {
1250 let Ok(value) = value.to_str() else {
1251 continue;
1252 };
1253 wire.entry(name.as_str().to_string())
1254 .or_default()
1255 .push(value.to_string());
1256 }
1257 wire
1258}