@@ -15,33 +15,19 @@ use axum::{
1515 Router ,
1616} ;
1717use serde_json:: Value ;
18- use tokio:: sync:: { mpsc, Mutex } ;
1918use tower_http:: cors:: { Any , CorsLayer } ;
2019
2120use crate :: server_factory:: AcpServer ;
2221
22+ pub ( crate ) const HEADER_CONNECTION_ID : & str = "Acp-Connection-Id" ;
2323pub ( crate ) const HEADER_SESSION_ID : & str = "Acp-Session-Id" ;
2424pub ( crate ) const EVENT_STREAM_MIME_TYPE : & str = "text/event-stream" ;
2525pub ( crate ) const JSON_MIME_TYPE : & str = "application/json" ;
2626
27- pub ( crate ) struct TransportSession {
28- pub to_agent_tx : mpsc:: Sender < String > ,
29- pub from_agent_rx : Arc < Mutex < mpsc:: UnboundedReceiver < String > > > ,
30- pub handle : tokio:: task:: JoinHandle < ( ) > ,
31- }
32-
33- pub ( crate ) fn accepts_mime_type ( request : & Request < Body > , mime_type : & str ) -> bool {
34- request
35- . headers ( )
36- . get ( axum:: http:: header:: ACCEPT )
37- . and_then ( |v| v. to_str ( ) . ok ( ) )
38- . is_some_and ( |accept| accept. contains ( mime_type) )
39- }
40-
4127pub ( crate ) fn accepts_json_and_sse ( request : & Request < Body > ) -> bool {
4228 request
4329 . headers ( )
44- . get ( axum :: http :: header:: ACCEPT )
30+ . get ( header:: ACCEPT )
4531 . and_then ( |v| v. to_str ( ) . ok ( ) )
4632 . is_some_and ( |accept| {
4733 accept. contains ( JSON_MIME_TYPE ) && accept. contains ( EVENT_STREAM_MIME_TYPE )
@@ -51,11 +37,19 @@ pub(crate) fn accepts_json_and_sse(request: &Request<Body>) -> bool {
5137pub ( crate ) fn content_type_is_json ( request : & Request < Body > ) -> bool {
5238 request
5339 . headers ( )
54- . get ( axum :: http :: header:: CONTENT_TYPE )
40+ . get ( header:: CONTENT_TYPE )
5541 . and_then ( |v| v. to_str ( ) . ok ( ) )
5642 . is_some_and ( |ct| ct. starts_with ( JSON_MIME_TYPE ) )
5743}
5844
45+ pub ( crate ) fn get_connection_id ( request : & Request < Body > ) -> Option < String > {
46+ request
47+ . headers ( )
48+ . get ( HEADER_CONNECTION_ID )
49+ . and_then ( |v| v. to_str ( ) . ok ( ) )
50+ . map ( |s| s. to_string ( ) )
51+ }
52+
5953pub ( crate ) fn get_session_id ( request : & Request < Body > ) -> Option < String > {
6054 request
6155 . headers ( )
@@ -64,20 +58,43 @@ pub(crate) fn get_session_id(request: &Request<Body>) -> Option<String> {
6458 . map ( |s| s. to_string ( ) )
6559}
6660
61+ pub ( crate ) fn is_initialize_request ( value : & Value ) -> bool {
62+ value. get ( "method" ) . is_some_and ( |m| m == "initialize" ) && value. get ( "id" ) . is_some ( )
63+ }
64+
65+ pub ( crate ) fn is_session_creating_request ( value : & Value ) -> bool {
66+ value
67+ . get ( "method" )
68+ . and_then ( |m| m. as_str ( ) )
69+ . is_some_and ( |m| m == "session/new" || m == "session/load" || m == "session/fork" )
70+ }
71+
6772pub ( crate ) fn is_jsonrpc_request ( value : & Value ) -> bool {
6873 value. get ( "method" ) . is_some ( ) && value. get ( "id" ) . is_some ( )
6974}
7075
71- pub ( crate ) fn is_jsonrpc_notification ( value : & Value ) -> bool {
72- value. get ( "method " ) . is_some ( ) && value. get ( "id " ) . is_none ( )
76+ pub ( crate ) fn is_jsonrpc_response_or_error ( value : & Value ) -> bool {
77+ value. get ( "id " ) . is_some ( ) && ( value. get ( "result " ) . is_some ( ) || value . get ( "error" ) . is_some ( ) )
7378}
7479
75- pub ( crate ) fn is_jsonrpc_response ( value : & Value ) -> bool {
76- value. get ( "id" ) . is_some ( ) && ( value. get ( "result" ) . is_some ( ) || value. get ( "error" ) . is_some ( ) )
80+ /// Extract the JSON-RPC `id` from a message. Returns the id as a string
81+ /// regardless of whether it was originally a number or string.
82+ pub ( crate ) fn extract_jsonrpc_id ( value : & Value ) -> Option < String > {
83+ value. get ( "id" ) . map ( |id| match id {
84+ Value :: String ( s) => s. clone ( ) ,
85+ Value :: Number ( n) => n. to_string ( ) ,
86+ other => other. to_string ( ) ,
87+ } )
7788}
7889
79- pub ( crate ) fn is_initialize_request ( value : & Value ) -> bool {
80- value. get ( "method" ) . is_some_and ( |m| m == "initialize" ) && value. get ( "id" ) . is_some ( )
90+ /// Extract `sessionId` from a JSON-RPC result body.
91+ /// Used by the transport to set `Acp-Session-Id` on session/new and session/load responses.
92+ pub ( crate ) fn extract_session_id_from_result ( value : & Value ) -> Option < String > {
93+ value
94+ . get ( "result" )
95+ . and_then ( |r| r. get ( "sessionId" ) )
96+ . and_then ( |s| s. as_str ( ) )
97+ . map ( |s| s. to_string ( ) )
8198}
8299
83100async fn handle_get (
@@ -99,13 +116,17 @@ pub fn create_router(server: Arc<AcpServer>) -> Router {
99116 let http_state = Arc :: new ( http:: HttpState :: new ( server. clone ( ) ) ) ;
100117 let ws_state = Arc :: new ( websocket:: WsState :: new ( server) ) ;
101118
119+ let connection_id_header = HEADER_CONNECTION_ID . parse ( ) . unwrap ( ) ;
120+ let session_id_header = HEADER_SESSION_ID . parse ( ) . unwrap ( ) ;
121+
102122 let cors = CorsLayer :: new ( )
103123 . allow_origin ( Any )
104124 . allow_methods ( [ Method :: GET , Method :: POST , Method :: DELETE , Method :: OPTIONS ] )
105125 . allow_headers ( [
106126 header:: CONTENT_TYPE ,
107127 header:: ACCEPT ,
108- HEADER_SESSION_ID . parse ( ) . unwrap ( ) ,
128+ connection_id_header,
129+ session_id_header,
109130 header:: SEC_WEBSOCKET_VERSION ,
110131 header:: SEC_WEBSOCKET_KEY ,
111132 header:: CONNECTION ,
0 commit comments