11use reqwest:: { Client as ReqwestClient , StatusCode } ;
22use serde:: de:: DeserializeOwned ;
33use serde_json:: Value ;
4+ use std:: fs:: File ;
5+ use std:: path:: Path ;
6+ use tempfile:: NamedTempFile ;
7+ use url:: Url ;
8+ use zip:: write:: FileOptions ;
9+ use zip:: CompressionMethod ;
410
511use crate :: error:: { Error , Result } ;
612
@@ -27,6 +33,82 @@ impl HttpClient {
2733 }
2834 }
2935
36+ /// Check if the server is localhost or 127.0.0.1
37+ fn is_local_server ( & self ) -> bool {
38+ if let Ok ( url) = Url :: parse ( & self . base_url ) {
39+ if let Some ( host) = url. host_str ( ) {
40+ return host == "localhost" || host == "127.0.0.1" ;
41+ }
42+ }
43+ false
44+ }
45+
46+ /// Zip a directory to a temporary file
47+ fn zip_directory ( & self , dir_path : & Path ) -> Result < NamedTempFile > {
48+ if !dir_path. is_dir ( ) {
49+ return Err ( Error :: Network ( format ! (
50+ "Path {} is not a directory" ,
51+ dir_path. display( )
52+ ) ) ) ;
53+ }
54+
55+ let temp_file = NamedTempFile :: new ( ) ?;
56+ let file = File :: create ( temp_file. path ( ) ) ?;
57+ let mut zip = zip:: ZipWriter :: new ( file) ;
58+ let options: FileOptions < ' _ , ( ) > = FileOptions :: default ( ) . compression_method ( CompressionMethod :: Deflated ) ;
59+
60+ let walkdir = walkdir:: WalkDir :: new ( dir_path) ;
61+ for entry in walkdir. into_iter ( ) . filter_map ( |e| e. ok ( ) ) {
62+ let path = entry. path ( ) ;
63+ if path. is_file ( ) {
64+ let name = path. strip_prefix ( dir_path) . unwrap_or ( path) ;
65+ zip. start_file ( name. to_string_lossy ( ) , options) ?;
66+ let mut file = File :: open ( path) ?;
67+ std:: io:: copy ( & mut file, & mut zip) ?;
68+ }
69+ }
70+
71+ zip. finish ( ) ?;
72+ Ok ( temp_file)
73+ }
74+
75+ /// Upload a temporary file and return the temp_path
76+ async fn upload_temp_file ( & self , file_path : & Path ) -> Result < String > {
77+ let url = format ! ( "{}/api/v1/resources/temp_upload" , self . base_url) ;
78+ let file_name = file_path
79+ . file_name ( )
80+ . and_then ( |n| n. to_str ( ) )
81+ . unwrap_or ( "temp_upload.zip" ) ;
82+
83+ // Read file content
84+ let file_content = tokio:: fs:: read ( file_path) . await ?;
85+
86+ // Create multipart form
87+ let part = reqwest:: multipart:: Part :: bytes ( file_content)
88+ . file_name ( file_name. to_string ( ) ) ;
89+
90+ let part = part. mime_str ( "application/octet-stream" ) . map_err ( |e| {
91+ Error :: Network ( format ! ( "Failed to set mime type: {}" , e) )
92+ } ) ?;
93+
94+ let form = reqwest:: multipart:: Form :: new ( ) . part ( "file" , part) ;
95+
96+ let response = self
97+ . http
98+ . post ( & url)
99+ . multipart ( form)
100+ . send ( )
101+ . await
102+ . map_err ( |e| Error :: Network ( format ! ( "HTTP request failed: {}" , e) ) ) ?;
103+
104+ let result: Value = self . handle_response ( response) . await ?;
105+ result
106+ . get ( "temp_path" )
107+ . and_then ( |v| v. as_str ( ) )
108+ . map ( |s| s. to_string ( ) )
109+ . ok_or_else ( || Error :: Parse ( "Missing temp_path in response" . to_string ( ) ) )
110+ }
111+
30112 fn build_headers ( & self ) -> reqwest:: header:: HeaderMap {
31113 let mut headers = reqwest:: header:: HeaderMap :: new ( ) ;
32114 headers. insert (
@@ -327,15 +409,37 @@ impl HttpClient {
327409 wait : bool ,
328410 timeout : Option < f64 > ,
329411 ) -> Result < serde_json:: Value > {
330- let body = serde_json:: json!( {
331- "path" : path,
332- "target" : target,
333- "reason" : reason,
334- "instruction" : instruction,
335- "wait" : wait,
336- "timeout" : timeout,
337- } ) ;
338- self . post ( "/api/v1/resources" , & body) . await
412+ let path_obj = Path :: new ( path) ;
413+
414+ // Check if it's a local directory and not a local server
415+ if path_obj. exists ( ) && path_obj. is_dir ( ) && !self . is_local_server ( ) {
416+ // Zip the directory
417+ let zip_file = self . zip_directory ( path_obj) ?;
418+ let temp_path = self . upload_temp_file ( zip_file. path ( ) ) . await ?;
419+
420+ let body = serde_json:: json!( {
421+ "temp_path" : temp_path,
422+ "target" : target,
423+ "reason" : reason,
424+ "instruction" : instruction,
425+ "wait" : wait,
426+ "timeout" : timeout,
427+ } ) ;
428+
429+ self . post ( "/api/v1/resources" , & body) . await
430+ } else {
431+ // Regular case - use path directly
432+ let body = serde_json:: json!( {
433+ "path" : path,
434+ "target" : target,
435+ "reason" : reason,
436+ "instruction" : instruction,
437+ "wait" : wait,
438+ "timeout" : timeout,
439+ } ) ;
440+
441+ self . post ( "/api/v1/resources" , & body) . await
442+ }
339443 }
340444
341445 pub async fn add_skill (
0 commit comments