@@ -44,13 +44,6 @@ class BrowserConnectRequest(BaseModel):
4444 connected_ports : list [int ] = Field (default_factory = list )
4545
4646
47- class BrowserLaunchChromeRequest (BaseModel ):
48- r"""Request model for launching user's Chrome with a specific profile."""
49-
50- profile_directory : str = Field (
51- description = "Chrome profile directory name, e.g. 'Default' or 'Profile 1'"
52- )
53-
5447
5548logger = logging .getLogger ("tool_controller" )
5649router = APIRouter ()
@@ -148,77 +141,6 @@ def _find_chromium_executable() -> str | None:
148141 return None
149142
150143
151- def _find_system_chrome () -> str | None :
152- """Find the user's installed Google Chrome executable path."""
153- system = pf .system ()
154-
155- if system == "Darwin" :
156- chrome_path = (
157- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
158- )
159- if os .path .exists (chrome_path ):
160- return chrome_path
161- elif system == "Linux" :
162- import shutil
163-
164- for name in ("google-chrome" , "google-chrome-stable" ):
165- found = shutil .which (name )
166- if found :
167- return found
168- elif system == "Windows" :
169- candidates = [
170- os .path .join (
171- os .environ .get ("PROGRAMFILES" , "C:\\ Program Files" ),
172- "Google" ,
173- "Chrome" ,
174- "Application" ,
175- "chrome.exe" ,
176- ),
177- os .path .join (
178- os .environ .get (
179- "PROGRAMFILES(X86)" ,
180- "C:\\ Program Files (x86)" ,
181- ),
182- "Google" ,
183- "Chrome" ,
184- "Application" ,
185- "chrome.exe" ,
186- ),
187- ]
188- for p in candidates :
189- if os .path .exists (p ):
190- return p
191-
192- return None
193-
194-
195- def _get_chrome_user_data_dir () -> str | None :
196- """Get the Chrome user data directory path for the current platform."""
197- home = os .path .expanduser ("~" )
198- system = pf .system ()
199-
200- if system == "Darwin" :
201- d = os .path .join (
202- home ,
203- "Library" ,
204- "Application Support" ,
205- "Google" ,
206- "Chrome" ,
207- )
208- elif system == "Linux" :
209- d = os .path .join (home , ".config" , "google-chrome" )
210- elif system == "Windows" :
211- local_app = os .environ .get (
212- "LOCALAPPDATA" ,
213- os .path .join (home , "AppData" , "Local" ),
214- )
215- d = os .path .join (local_app , "Google" , "Chrome" , "User Data" )
216- else :
217- return None
218-
219- return d if os .path .isdir (d ) else None
220-
221-
222144@router .post ("/install/tool/{tool}" , name = "install tool" )
223145async def install_tool (tool : str ):
224146 """
@@ -1100,175 +1022,6 @@ async def launch_cdp_browser():
11001022 )
11011023
11021024
1103- @router .post (
1104- "/browser/launch-chrome" ,
1105- name = "launch user Chrome with profile" ,
1106- )
1107- async def launch_chrome_with_profile (
1108- request : BrowserLaunchChromeRequest ,
1109- ):
1110- """
1111- Launch the user's installed Google Chrome with a specific
1112- profile and CDP remote debugging enabled.
1113-
1114- Args:
1115- request.profile_directory: Chrome profile dir name
1116- (e.g. 'Default', 'Profile 1')
1117-
1118- Returns:
1119- success: Whether the launch was successful
1120- port: The assigned CDP port number
1121- data: Browser CDP info (from /json/version)
1122- """
1123- try :
1124- import asyncio
1125- import shutil
1126-
1127- import httpx
1128-
1129- # 1. Find system Chrome
1130- chrome_executable = _find_system_chrome ()
1131- if not chrome_executable :
1132- raise HTTPException (
1133- status_code = 404 ,
1134- detail = "Google Chrome is not installed on this system" ,
1135- )
1136-
1137- # 2. Verify Chrome user data directory exists
1138- chrome_user_data_dir = _get_chrome_user_data_dir ()
1139- if not chrome_user_data_dir :
1140- raise HTTPException (
1141- status_code = 404 ,
1142- detail = "Chrome user data directory not found" ,
1143- )
1144-
1145- # 3. Verify the requested profile exists
1146- profile_path = os .path .join (
1147- chrome_user_data_dir , request .profile_directory
1148- )
1149- if not os .path .isdir (profile_path ):
1150- raise HTTPException (
1151- status_code = 404 ,
1152- detail = (f"Profile '{ request .profile_directory } ' not found" ),
1153- )
1154-
1155- # 4. Find available port
1156- port = _find_available_cdp_port ()
1157- if port is None :
1158- raise HTTPException (
1159- status_code = 503 ,
1160- detail = (
1161- "No available port found in range"
1162- f" { _CDP_LAUNCH_PORT_START } -{ _CDP_PORT_END } "
1163- ),
1164- )
1165-
1166- logger .info (
1167- "[CHROME LAUNCH] Launching Chrome profile"
1168- f" '{ request .profile_directory } ' on port { port } "
1169- )
1170-
1171- # 5. Chrome refuses CDP on its default data dir, so
1172- # create a wrapper dir and symlink the real profile.
1173- wrapper_dir = os .path .join (
1174- os .path .expanduser ("~" ),
1175- ".eigent" ,
1176- "chrome_cdp" ,
1177- )
1178- os .makedirs (wrapper_dir , exist_ok = True )
1179-
1180- # Symlink requested profile into wrapper
1181- link_dst = os .path .join (wrapper_dir , request .profile_directory )
1182- real_profile = os .path .join (
1183- chrome_user_data_dir , request .profile_directory
1184- )
1185- if os .path .islink (link_dst ):
1186- os .remove (link_dst )
1187- elif os .path .isdir (link_dst ):
1188- shutil .rmtree (link_dst )
1189- os .symlink (real_profile , link_dst )
1190-
1191- # Copy Local State (small JSON, needed by Chrome)
1192- local_state_src = os .path .join (chrome_user_data_dir , "Local State" )
1193- local_state_dst = os .path .join (wrapper_dir , "Local State" )
1194- if os .path .isfile (local_state_src ):
1195- shutil .copy2 (local_state_src , local_state_dst )
1196-
1197- args = [
1198- chrome_executable ,
1199- f"--remote-debugging-port={ port } " ,
1200- f"--user-data-dir={ wrapper_dir } " ,
1201- f"--profile-directory={ request .profile_directory } " ,
1202- "--no-first-run" ,
1203- "--no-default-browser-check" ,
1204- ]
1205-
1206- process = subprocess .Popen (
1207- args ,
1208- stdout = subprocess .DEVNULL ,
1209- stderr = subprocess .DEVNULL ,
1210- )
1211-
1212- _launched_browser_processes [port ] = process
1213- logger .info (f"[CHROME LAUNCH] Chrome spawned, PID: { process .pid } " )
1214-
1215- # 6. Poll for browser readiness (max 8 seconds,
1216- # real Chrome may be slower than Chromium)
1217- max_wait = 8.0
1218- poll_interval = 0.3
1219- start_time = time .time ()
1220- browser_info = None
1221-
1222- async with httpx .AsyncClient () as client :
1223- while time .time () - start_time < max_wait :
1224- try :
1225- resp = await client .get (
1226- f"http://localhost:{ port } /json/version" ,
1227- timeout = 1.0 ,
1228- )
1229- if resp .status_code == 200 :
1230- browser_info = resp .json ()
1231- break
1232- except Exception :
1233- pass
1234- await asyncio .sleep (poll_interval )
1235-
1236- if browser_info is None :
1237- process .kill ()
1238- _launched_browser_processes .pop (port , None )
1239- elapsed = time .time () - start_time
1240- raise HTTPException (
1241- status_code = 409 ,
1242- detail = (f"Chrome failed to start CDP (waited { elapsed :.1f} s)" ),
1243- )
1244-
1245- elapsed = time .time () - start_time
1246- logger .info (
1247- f"[CHROME LAUNCH] Chrome ready on port { port } after { elapsed :.1f} s"
1248- )
1249-
1250- return {
1251- "success" : True ,
1252- "port" : port ,
1253- "data" : browser_info ,
1254- "profile_directory" : request .profile_directory ,
1255- "message" : (
1256- f"Chrome launched with profile"
1257- f" '{ request .profile_directory } '"
1258- f" on port { port } "
1259- ),
1260- }
1261-
1262- except HTTPException :
1263- raise
1264- except Exception as e :
1265- logger .error (f"[CHROME LAUNCH] Failed: { e } " , exc_info = True )
1266- raise HTTPException (
1267- status_code = 500 ,
1268- detail = f"Failed to launch Chrome: { e !s} " ,
1269- )
1270-
1271-
12721025@router .get ("/browser/cookies" , name = "list cookie domains" )
12731026async def list_cookie_domains (search : str = None ):
12741027 """
0 commit comments