@@ -4,8 +4,8 @@ use serde_json::Value;
44use std:: fs:: File ;
55use std:: path:: Path ;
66use tempfile:: NamedTempFile ;
7- use zip:: write:: FileOptions ;
87use zip:: CompressionMethod ;
8+ use zip:: write:: FileOptions ;
99
1010use crate :: error:: { Error , Result } ;
1111
@@ -15,6 +15,8 @@ pub struct HttpClient {
1515 http : ReqwestClient ,
1616 base_url : String ,
1717 api_key : Option < String > ,
18+ account : Option < String > ,
19+ user : Option < String > ,
1820 agent_id : Option < String > ,
1921}
2022
@@ -24,6 +26,8 @@ impl HttpClient {
2426 base_url : impl Into < String > ,
2527 api_key : Option < String > ,
2628 agent_id : Option < String > ,
29+ account : Option < String > ,
30+ user : Option < String > ,
2731 timeout_secs : f64 ,
2832 ) -> Self {
2933 let http = ReqwestClient :: builder ( )
@@ -35,6 +39,8 @@ impl HttpClient {
3539 http,
3640 base_url : base_url. into ( ) . trim_end_matches ( '/' ) . to_string ( ) ,
3741 api_key,
42+ account,
43+ user,
3844 agent_id,
3945 }
4046 }
@@ -51,7 +57,8 @@ impl HttpClient {
5157 let temp_file = NamedTempFile :: new ( ) ?;
5258 let file = File :: create ( temp_file. path ( ) ) ?;
5359 let mut zip = zip:: ZipWriter :: new ( file) ;
54- let options: FileOptions < ' _ , ( ) > = FileOptions :: default ( ) . compression_method ( CompressionMethod :: Deflated ) ;
60+ let options: FileOptions < ' _ , ( ) > =
61+ FileOptions :: default ( ) . compression_method ( CompressionMethod :: Deflated ) ;
5562
5663 let walkdir = walkdir:: WalkDir :: new ( dir_path) ;
5764 for entry in walkdir. into_iter ( ) . filter_map ( |e| e. ok ( ) ) {
@@ -78,14 +85,13 @@ impl HttpClient {
7885
7986 // Read file content
8087 let file_content = tokio:: fs:: read ( file_path) . await ?;
81-
88+
8289 // Create multipart form
83- let part = reqwest:: multipart:: Part :: bytes ( file_content)
84- . file_name ( file_name. to_string ( ) ) ;
85-
86- let part = part. mime_str ( "application/octet-stream" ) . map_err ( |e| {
87- Error :: Network ( format ! ( "Failed to set mime type: {}" , e) )
88- } ) ?;
90+ let part = reqwest:: multipart:: Part :: bytes ( file_content) . file_name ( file_name. to_string ( ) ) ;
91+
92+ let part = part
93+ . mime_str ( "application/octet-stream" )
94+ . map_err ( |e| Error :: Network ( format ! ( "Failed to set mime type: {}" , e) ) ) ?;
8995
9096 let form = reqwest:: multipart:: Form :: new ( ) . part ( "file" , part) ;
9197
@@ -126,6 +132,16 @@ impl HttpClient {
126132 headers. insert ( "X-OpenViking-Agent" , value) ;
127133 }
128134 }
135+ if let Some ( account) = & self . account {
136+ if let Ok ( value) = reqwest:: header:: HeaderValue :: from_str ( account) {
137+ headers. insert ( "X-OpenViking-Account" , value) ;
138+ }
139+ }
140+ if let Some ( user) = & self . user {
141+ if let Ok ( value) = reqwest:: header:: HeaderValue :: from_str ( user) {
142+ headers. insert ( "X-OpenViking-User" , value) ;
143+ }
144+ }
129145 headers
130146 }
131147
@@ -224,10 +240,7 @@ impl HttpClient {
224240 self . handle_response ( response) . await
225241 }
226242
227- async fn handle_response < T : DeserializeOwned > (
228- & self ,
229- response : reqwest:: Response ,
230- ) -> Result < T > {
243+ async fn handle_response < T : DeserializeOwned > ( & self , response : reqwest:: Response ) -> Result < T > {
231244 let status = response. status ( ) ;
232245
233246 // Handle empty response (204 No Content, etc.)
@@ -248,7 +261,11 @@ impl HttpClient {
248261 . and_then ( |e| e. get ( "message" ) )
249262 . and_then ( |m| m. as_str ( ) )
250263 . map ( |s| s. to_string ( ) )
251- . or_else ( || json. get ( "detail" ) . and_then ( |d| d. as_str ( ) ) . map ( |s| s. to_string ( ) ) )
264+ . or_else ( || {
265+ json. get ( "detail" )
266+ . and_then ( |d| d. as_str ( ) )
267+ . map ( |s| s. to_string ( ) )
268+ } )
252269 . unwrap_or_else ( || format ! ( "HTTP error {}" , status) ) ;
253270 return Err ( Error :: Api ( error_msg) ) ;
254271 }
@@ -296,7 +313,12 @@ impl HttpClient {
296313 self . get ( "/api/v1/content/overview" , & params) . await
297314 }
298315
299- pub async fn reindex ( & self , uri : & str , regenerate : bool , wait : bool ) -> Result < serde_json:: Value > {
316+ pub async fn reindex (
317+ & self ,
318+ uri : & str ,
319+ regenerate : bool ,
320+ wait : bool ,
321+ ) -> Result < serde_json:: Value > {
300322 let body = serde_json:: json!( {
301323 "uri" : uri,
302324 "regenerate" : regenerate,
@@ -309,7 +331,7 @@ impl HttpClient {
309331 pub async fn get_bytes ( & self , uri : & str ) -> Result < Vec < u8 > > {
310332 let url = format ! ( "{}/api/v1/content/download" , self . base_url) ;
311333 let params = vec ! [ ( "uri" . to_string( ) , uri. to_string( ) ) ] ;
312-
334+
313335 let response = self
314336 . http
315337 . get ( & url)
@@ -326,20 +348,22 @@ impl HttpClient {
326348 . json ( )
327349 . await
328350 . map_err ( |e| Error :: Network ( format ! ( "Failed to parse error response: {}" , e) ) ) ;
329-
351+
330352 let error_msg = match json_result {
331- Ok ( json) => {
332- json
333- . get ( "error" )
334- . and_then ( |e| e. get ( "message" ) )
335- . and_then ( |m| m. as_str ( ) )
336- . map ( |s| s. to_string ( ) )
337- . or_else ( || json. get ( "detail" ) . and_then ( |d| d. as_str ( ) ) . map ( |s| s. to_string ( ) ) )
338- . unwrap_or_else ( || format ! ( "HTTP error {}" , status) )
339- }
353+ Ok ( json) => json
354+ . get ( "error" )
355+ . and_then ( |e| e. get ( "message" ) )
356+ . and_then ( |m| m. as_str ( ) )
357+ . map ( |s| s. to_string ( ) )
358+ . or_else ( || {
359+ json. get ( "detail" )
360+ . and_then ( |d| d. as_str ( ) )
361+ . map ( |s| s. to_string ( ) )
362+ } )
363+ . unwrap_or_else ( || format ! ( "HTTP error {}" , status) ) ,
340364 Err ( _) => format ! ( "HTTP error {}" , status) ,
341365 } ;
342-
366+
343367 return Err ( Error :: Api ( error_msg) ) ;
344368 }
345369
@@ -352,7 +376,16 @@ impl HttpClient {
352376
353377 // ============ Filesystem Methods ============
354378
355- pub async fn ls ( & self , uri : & str , simple : bool , recursive : bool , output : & str , abs_limit : i32 , show_all_hidden : bool , node_limit : i32 ) -> Result < serde_json:: Value > {
379+ pub async fn ls (
380+ & self ,
381+ uri : & str ,
382+ simple : bool ,
383+ recursive : bool ,
384+ output : & str ,
385+ abs_limit : i32 ,
386+ show_all_hidden : bool ,
387+ node_limit : i32 ,
388+ ) -> Result < serde_json:: Value > {
356389 let params = vec ! [
357390 ( "uri" . to_string( ) , uri. to_string( ) ) ,
358391 ( "simple" . to_string( ) , simple. to_string( ) ) ,
@@ -365,7 +398,15 @@ impl HttpClient {
365398 self . get ( "/api/v1/fs/ls" , & params) . await
366399 }
367400
368- pub async fn tree ( & self , uri : & str , output : & str , abs_limit : i32 , show_all_hidden : bool , node_limit : i32 , level_limit : i32 ) -> Result < serde_json:: Value > {
401+ pub async fn tree (
402+ & self ,
403+ uri : & str ,
404+ output : & str ,
405+ abs_limit : i32 ,
406+ show_all_hidden : bool ,
407+ node_limit : i32 ,
408+ level_limit : i32 ,
409+ ) -> Result < serde_json:: Value > {
369410 let params = vec ! [
370411 ( "uri" . to_string( ) , uri. to_string( ) ) ,
371412 ( "output" . to_string( ) , output. to_string( ) ) ,
@@ -442,7 +483,13 @@ impl HttpClient {
442483 self . post ( "/api/v1/search/search" , & body) . await
443484 }
444485
445- pub async fn grep ( & self , uri : & str , pattern : & str , ignore_case : bool , node_limit : i32 ) -> Result < serde_json:: Value > {
486+ pub async fn grep (
487+ & self ,
488+ uri : & str ,
489+ pattern : & str ,
490+ ignore_case : bool ,
491+ node_limit : i32 ,
492+ ) -> Result < serde_json:: Value > {
446493 let body = serde_json:: json!( {
447494 "uri" : uri,
448495 "pattern" : pattern,
@@ -452,8 +499,12 @@ impl HttpClient {
452499 self . post ( "/api/v1/search/grep" , & body) . await
453500 }
454501
455-
456- pub async fn glob ( & self , pattern : & str , uri : & str , node_limit : i32 ) -> Result < serde_json:: Value > {
502+ pub async fn glob (
503+ & self ,
504+ pattern : & str ,
505+ uri : & str ,
506+ node_limit : i32 ,
507+ ) -> Result < serde_json:: Value > {
457508 let body = serde_json:: json!( {
458509 "pattern" : pattern,
459510 "uri" : uri,
@@ -726,11 +777,7 @@ impl HttpClient {
726777 self . put ( & path, & body) . await
727778 }
728779
729- pub async fn admin_regenerate_key (
730- & self ,
731- account_id : & str ,
732- user_id : & str ,
733- ) -> Result < Value > {
780+ pub async fn admin_regenerate_key ( & self , account_id : & str , user_id : & str ) -> Result < Value > {
734781 let path = format ! (
735782 "/api/v1/admin/accounts/{}/users/{}/key" ,
736783 account_id, user_id
@@ -790,3 +837,47 @@ impl HttpClient {
790837 Ok ( count)
791838 }
792839}
840+
841+ #[ cfg( test) ]
842+ mod tests {
843+ use super :: HttpClient ;
844+
845+ #[ test]
846+ fn build_headers_includes_tenant_identity_headers ( ) {
847+ let client = HttpClient :: new (
848+ "http://localhost:1933" ,
849+ Some ( "test-key" . to_string ( ) ) ,
850+ Some ( "assistant-1" . to_string ( ) ) ,
851+ Some ( "acme" . to_string ( ) ) ,
852+ Some ( "alice" . to_string ( ) ) ,
853+ 5.0 ,
854+ ) ;
855+
856+ let headers = client. build_headers ( ) ;
857+
858+ assert_eq ! (
859+ headers
860+ . get( "X-API-Key" )
861+ . and_then( |value| value. to_str( ) . ok( ) ) ,
862+ Some ( "test-key" )
863+ ) ;
864+ assert_eq ! (
865+ headers
866+ . get( "X-OpenViking-Agent" )
867+ . and_then( |value| value. to_str( ) . ok( ) ) ,
868+ Some ( "assistant-1" )
869+ ) ;
870+ assert_eq ! (
871+ headers
872+ . get( "X-OpenViking-Account" )
873+ . and_then( |value| value. to_str( ) . ok( ) ) ,
874+ Some ( "acme" )
875+ ) ;
876+ assert_eq ! (
877+ headers
878+ . get( "X-OpenViking-User" )
879+ . and_then( |value| value. to_str( ) . ok( ) ) ,
880+ Some ( "alice" )
881+ ) ;
882+ }
883+ }
0 commit comments