33
44This module provides the client for the V4 version of the AgentOps API.
55"""
6-
7- from typing import Optional , Union , Dict
8-
6+ from typing import Optional , Union , Dict , Set
97from agentops .client .api .base import BaseApiClient
108from agentops .exceptions import ApiServerException
119from agentops .client .api .types import UploadedObjectResponse
1210from agentops .helpers .version import get_agentops_version
11+ from agentops .logging import logger
12+ import os
13+ import sys
1314
1415
1516class V4Client (BaseApiClient ):
1617 """Client for the AgentOps V4 API"""
1718
1819 auth_token : str
20+ _collected_files : Set [str ] = set ()
21+ _instance : Optional ["V4Client" ] = None
22+
23+ def __init__ (self , * args , ** kwargs ):
24+ """Initialize the V4Client."""
25+ super ().__init__ (* args , ** kwargs )
26+ V4Client ._instance = self
1927
2028 def set_auth_token (self , token : str ):
2129 """
@@ -26,6 +34,11 @@ def set_auth_token(self, token: str):
2634 """
2735 self .auth_token = token
2836
37+ @classmethod
38+ def get_instance (cls ) -> Optional ["V4Client" ]:
39+ """Get the current V4Client instance."""
40+ return cls ._instance
41+
2942 def prepare_headers (self , custom_headers : Optional [Dict [str , str ]] = None ) -> Dict [str , str ]:
3043 """
3144 Prepare headers for API requests.
@@ -102,3 +115,121 @@ def upload_logfile(self, body: Union[str, bytes], trace_id: int) -> UploadedObje
102115 return UploadedObjectResponse (** response_data )
103116 except Exception as e :
104117 raise ApiServerException (f"Failed to process upload response: { str (e )} " )
118+
119+ def upload_file_content (self , filepath : str , content : str ) -> Optional [UploadedObjectResponse ]:
120+ """
121+ Upload file content to the API using the scripts upload endpoint.
122+
123+ Args:
124+ filepath: The path of the file being uploaded
125+ content: The content of the file to upload
126+ Returns:
127+ UploadedObjectResponse if successful, None if failed
128+ """
129+ try :
130+ # Create a structured payload with file metadata for script upload
131+ file_data = {
132+ "filepath" : filepath ,
133+ "content" : content ,
134+ "filename" : os .path .basename (filepath ),
135+ "type" : "source_file" ,
136+ }
137+
138+ # Use the scripts upload endpoint instead of objects upload
139+ response = self .post ("/scripts/upload/" , file_data , self .prepare_headers ())
140+
141+ if response .status_code != 200 :
142+ error_msg = f"Script upload failed: { response .status_code } "
143+ try :
144+ error_data = response .json ()
145+ if "error" in error_data :
146+ error_msg = error_data ["error" ]
147+ except Exception :
148+ pass
149+ logger .error (f"Script upload failed: { error_msg } " )
150+ return None
151+
152+ try :
153+ response_data = response .json ()
154+ upload_response = UploadedObjectResponse (** response_data )
155+ return upload_response
156+ except Exception as e :
157+ logger .error (f"Failed to process upload response for { filepath } : { str (e )} " )
158+ return None
159+
160+ except Exception :
161+ return None
162+
163+ @staticmethod
164+ def _is_user_file (filepath : str ) -> bool :
165+ """Check if the given filepath is a user .py file."""
166+ if not filepath or not filepath .endswith (".py" ):
167+ return False
168+ if "site-packages" in filepath or "dist-packages" in filepath :
169+ return False
170+ if not os .path .exists (filepath ):
171+ return False
172+ return True
173+
174+ @staticmethod
175+ def _read_file_content (filepath : str ) -> Optional [str ]:
176+ """
177+ Safely read file content with proper encoding handling.
178+
179+ Args:
180+ filepath: Path to the file to read
181+ Returns:
182+ File content as string, or None if reading failed
183+ """
184+ try :
185+ # Try UTF-8 first
186+ with open (filepath , "r" , encoding = "utf-8" ) as f :
187+ return f .read ()
188+ except UnicodeDecodeError :
189+ try :
190+ # Fallback to latin-1 for files with special characters
191+ with open (filepath , "r" , encoding = "latin-1" ) as f :
192+ return f .read ()
193+ except Exception as e :
194+ logger .error (f"Failed to read file { filepath } with latin-1: { e } " )
195+ return None
196+ except Exception as e :
197+ logger .error (f"Failed to read file { filepath } : { e } " )
198+ return None
199+
200+ @staticmethod
201+ def _normalize (path : str ) -> str :
202+ """Normalize the given path to an absolute path."""
203+ return os .path .abspath (os .path .realpath (path ))
204+
205+ @staticmethod
206+ def collect_from_argv ():
207+ """Collects the entrypoint file (typically from sys.argv[0])."""
208+ if len (sys .argv ) == 0 :
209+ return
210+ entry_file = V4Client ._normalize (sys .argv [0 ])
211+ if V4Client ._is_user_file (entry_file ):
212+ V4Client ._collected_files .add (entry_file )
213+
214+ @staticmethod
215+ def collect_all ():
216+ """Run all collection strategies and upload file contents."""
217+ V4Client .collect_from_argv ()
218+
219+ # Get the client instance to upload files
220+ client = V4Client .get_instance ()
221+ if not client :
222+ logger .error ("No V4Client instance available for file upload" )
223+ return
224+
225+ # Read and upload each collected file
226+ uploaded_count = 0
227+ for filepath in V4Client ._collected_files :
228+ content = V4Client ._read_file_content (filepath )
229+ if content is not None :
230+ response = client .upload_file_content (filepath , content )
231+ if response :
232+ uploaded_count += 1
233+ logger .info (f"Uploaded file: { filepath } " )
234+ else :
235+ logger .error (f"Failed to upload file: { filepath } " )
0 commit comments