lybic-guiagents 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lybic-guiagents might be problematic. Click here for more details.

Files changed (85) hide show
  1. desktop_env/__init__.py +1 -0
  2. desktop_env/actions.py +203 -0
  3. desktop_env/controllers/__init__.py +0 -0
  4. desktop_env/controllers/python.py +471 -0
  5. desktop_env/controllers/setup.py +882 -0
  6. desktop_env/desktop_env.py +509 -0
  7. desktop_env/evaluators/__init__.py +5 -0
  8. desktop_env/evaluators/getters/__init__.py +41 -0
  9. desktop_env/evaluators/getters/calc.py +15 -0
  10. desktop_env/evaluators/getters/chrome.py +1774 -0
  11. desktop_env/evaluators/getters/file.py +154 -0
  12. desktop_env/evaluators/getters/general.py +42 -0
  13. desktop_env/evaluators/getters/gimp.py +38 -0
  14. desktop_env/evaluators/getters/impress.py +126 -0
  15. desktop_env/evaluators/getters/info.py +24 -0
  16. desktop_env/evaluators/getters/misc.py +406 -0
  17. desktop_env/evaluators/getters/replay.py +20 -0
  18. desktop_env/evaluators/getters/vlc.py +86 -0
  19. desktop_env/evaluators/getters/vscode.py +35 -0
  20. desktop_env/evaluators/metrics/__init__.py +160 -0
  21. desktop_env/evaluators/metrics/basic_os.py +68 -0
  22. desktop_env/evaluators/metrics/chrome.py +493 -0
  23. desktop_env/evaluators/metrics/docs.py +1011 -0
  24. desktop_env/evaluators/metrics/general.py +665 -0
  25. desktop_env/evaluators/metrics/gimp.py +637 -0
  26. desktop_env/evaluators/metrics/libreoffice.py +28 -0
  27. desktop_env/evaluators/metrics/others.py +92 -0
  28. desktop_env/evaluators/metrics/pdf.py +31 -0
  29. desktop_env/evaluators/metrics/slides.py +957 -0
  30. desktop_env/evaluators/metrics/table.py +585 -0
  31. desktop_env/evaluators/metrics/thunderbird.py +176 -0
  32. desktop_env/evaluators/metrics/utils.py +719 -0
  33. desktop_env/evaluators/metrics/vlc.py +524 -0
  34. desktop_env/evaluators/metrics/vscode.py +283 -0
  35. desktop_env/providers/__init__.py +35 -0
  36. desktop_env/providers/aws/__init__.py +0 -0
  37. desktop_env/providers/aws/manager.py +278 -0
  38. desktop_env/providers/aws/provider.py +186 -0
  39. desktop_env/providers/aws/provider_with_proxy.py +315 -0
  40. desktop_env/providers/aws/proxy_pool.py +193 -0
  41. desktop_env/providers/azure/__init__.py +0 -0
  42. desktop_env/providers/azure/manager.py +87 -0
  43. desktop_env/providers/azure/provider.py +207 -0
  44. desktop_env/providers/base.py +97 -0
  45. desktop_env/providers/gcp/__init__.py +0 -0
  46. desktop_env/providers/gcp/manager.py +0 -0
  47. desktop_env/providers/gcp/provider.py +0 -0
  48. desktop_env/providers/virtualbox/__init__.py +0 -0
  49. desktop_env/providers/virtualbox/manager.py +463 -0
  50. desktop_env/providers/virtualbox/provider.py +124 -0
  51. desktop_env/providers/vmware/__init__.py +0 -0
  52. desktop_env/providers/vmware/manager.py +455 -0
  53. desktop_env/providers/vmware/provider.py +105 -0
  54. gui_agents/__init__.py +0 -0
  55. gui_agents/agents/Action.py +209 -0
  56. gui_agents/agents/__init__.py +0 -0
  57. gui_agents/agents/agent_s.py +832 -0
  58. gui_agents/agents/global_state.py +610 -0
  59. gui_agents/agents/grounding.py +651 -0
  60. gui_agents/agents/hardware_interface.py +129 -0
  61. gui_agents/agents/manager.py +568 -0
  62. gui_agents/agents/translator.py +132 -0
  63. gui_agents/agents/worker.py +355 -0
  64. gui_agents/cli_app.py +560 -0
  65. gui_agents/core/__init__.py +0 -0
  66. gui_agents/core/engine.py +1496 -0
  67. gui_agents/core/knowledge.py +449 -0
  68. gui_agents/core/mllm.py +555 -0
  69. gui_agents/tools/__init__.py +0 -0
  70. gui_agents/tools/tools.py +727 -0
  71. gui_agents/unit_test/__init__.py +0 -0
  72. gui_agents/unit_test/run_tests.py +65 -0
  73. gui_agents/unit_test/test_manager.py +330 -0
  74. gui_agents/unit_test/test_worker.py +269 -0
  75. gui_agents/utils/__init__.py +0 -0
  76. gui_agents/utils/analyze_display.py +301 -0
  77. gui_agents/utils/common_utils.py +263 -0
  78. gui_agents/utils/display_viewer.py +281 -0
  79. gui_agents/utils/embedding_manager.py +53 -0
  80. gui_agents/utils/image_axis_utils.py +27 -0
  81. lybic_guiagents-0.1.0.dist-info/METADATA +416 -0
  82. lybic_guiagents-0.1.0.dist-info/RECORD +85 -0
  83. lybic_guiagents-0.1.0.dist-info/WHEEL +5 -0
  84. lybic_guiagents-0.1.0.dist-info/licenses/LICENSE +201 -0
  85. lybic_guiagents-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,283 @@
1
+ import copy
2
+ import importlib.util
3
+ import json
4
+ import sys
5
+ import re
6
+ from typing import Dict
7
+
8
+
9
+ def check_json_keybindings(actual: str, expected: str, **options) -> float:
10
+ """
11
+ Args:
12
+ actual (str): path to result text file
13
+ expected (str): expected dict{}
14
+
15
+ Return:
16
+ float: the score
17
+ """
18
+
19
+ def direct_load_json(fp):
20
+ try:
21
+ with open(fp, 'r') as f:
22
+ data = json.load(f)
23
+ return data
24
+ except:
25
+ return None
26
+
27
+ def skip_first_line_load_json(fp):
28
+ try:
29
+ with open(fp, 'r') as f:
30
+ f.readline()
31
+ data = json.load(f)
32
+ return data
33
+ except:
34
+ return None
35
+
36
+ for func in [direct_load_json, skip_first_line_load_json]:
37
+ data = func(actual)
38
+ if data is not None and type(data) == list:
39
+ break
40
+ else:
41
+ return 0.0
42
+ expected = expected['expected']
43
+ if expected in data:
44
+ return 1.0
45
+ else:
46
+ return 0.0
47
+
48
+
49
+ def check_json_settings(actual: str, expected: str, **options) -> float:
50
+ """
51
+ Args:
52
+ actual (str): path to result text file
53
+ expected (dict): expected dict{}, containing key "expect"
54
+
55
+ Return:
56
+ float: the score
57
+ """
58
+ if not actual:
59
+ return 0.
60
+
61
+ try:
62
+ with open(actual, 'r') as f:
63
+ data = json.load(f)
64
+ except Exception as e:
65
+ return 0.0
66
+
67
+ expect = expected['expected']
68
+
69
+ # Check if all expected key-value pairs are in the actual data
70
+ for key, value in expect.items():
71
+ if key not in data or data[key] != value:
72
+ return 0.0
73
+
74
+ return 1.0
75
+
76
+
77
+ def compare_text_file(actual: str, expected: str, **options) -> float:
78
+ """
79
+ Args:
80
+ actual (str): path to result text file
81
+ expected (str): path to gold text file
82
+
83
+ Return:
84
+ float: the score
85
+ """
86
+ if not actual:
87
+ return 0.
88
+
89
+ with open(actual) as f1:
90
+ actual_text = f1.read()
91
+ with open(expected) as f2:
92
+ expected_text = f2.read()
93
+
94
+ ignore_blanks = options.get('ignore_blanks', False)
95
+ if ignore_blanks:
96
+ actual_text = re.sub(r'[\t\n]', ' ', actual_text).strip()
97
+ actual_text = re.sub(r'\s+', ' ', actual_text)
98
+ expected_text = re.sub(r'[\t\n]', ' ', expected_text).strip()
99
+ expected_text = re.sub(r'\s+', ' ', expected_text)
100
+
101
+ ignore_case = options.get('ignore_case', False)
102
+ if ignore_case:
103
+ actual_text = actual_text.lower()
104
+ expected_text = expected_text.lower()
105
+
106
+ if actual_text == expected_text:
107
+ return 1.0
108
+ return 0.0
109
+
110
+ import zipfile
111
+ from difflib import SequenceMatcher
112
+ import PyPDF2
113
+
114
+ def compare_pdf_content(content1, content2, text_similarity_threshold):
115
+ def extract_text_from_pdf(content):
116
+ with open("temp.pdf", "wb") as temp_pdf:
117
+ temp_pdf.write(content)
118
+ with open("temp.pdf", "rb") as temp_pdf:
119
+ pdf_reader = PyPDF2.PdfReader(temp_pdf)
120
+ text = ''
121
+ for page_num in range(len(pdf_reader.pages)):
122
+ page = pdf_reader.pages[page_num]
123
+ text += page.extract_text()
124
+ return text
125
+
126
+ text1 = extract_text_from_pdf(content1)
127
+ text2 = extract_text_from_pdf(content2)
128
+
129
+ similarity_ratio = SequenceMatcher(None, text1, text2).ratio()
130
+
131
+ return similarity_ratio >= text_similarity_threshold
132
+
133
+ def compare_zip_files(actual: str, expected: str, **options) -> float:
134
+ """
135
+ Args:
136
+ actual (str): path to result zip file
137
+ expected (str): path to gold zip file
138
+
139
+ Return:
140
+ float: the score
141
+ """
142
+ if not actual:
143
+ return 0.
144
+
145
+ with zipfile.ZipFile(actual, 'r') as zip_file1, zipfile.ZipFile(expected, 'r') as zip_file2:
146
+ file_list1 = set(zip_file1.namelist())
147
+ file_list2 = set(zip_file2.namelist())
148
+
149
+ if file_list1 != file_list2:
150
+ return 0.0
151
+
152
+ for file_name in file_list1:
153
+ content1 = zip_file1.read(file_name)
154
+ content2 = zip_file2.read(file_name)
155
+
156
+ if file_name.lower().endswith('.pdf'):
157
+ if compare_pdf_content(content1, content2, 0.95):
158
+ continue
159
+ else:
160
+ return 0.0
161
+ elif content1 != content2:
162
+ return 0.0
163
+ return 1.0
164
+
165
+
166
+ def compare_config(actual: str, rules: Dict, **options) -> float:
167
+ if not actual:
168
+ return 0.
169
+
170
+ with open(actual) as f1:
171
+ actual_text = f1.read()
172
+
173
+ if actual_text == rules['expected']:
174
+ return 1.0
175
+ return 0.0
176
+
177
+
178
+ def compare_answer(actual: str, rules: Dict, **options) -> float:
179
+ """
180
+ Args:
181
+ actual (str): result string
182
+ expected (str): gold string
183
+
184
+ Return:
185
+ float: the score
186
+ """
187
+ if not actual:
188
+ return 0.
189
+
190
+ if actual == rules['expected']:
191
+ return 1.0
192
+
193
+ # TODO: can use text embedding to get non-zero return
194
+ return 0.0
195
+
196
+
197
+ def is_extension_installed(actual: str, rules: Dict, **options):
198
+ if rules['type'] == 'contain':
199
+ if rules['expected'] in actual:
200
+ return 1.0
201
+ return 0.0
202
+ elif rules['type'] == 'not_contain':
203
+ if rules['expected'] not in actual:
204
+ return 1.0
205
+ return 0.0
206
+ else:
207
+ raise NotImplementedError
208
+
209
+
210
+ def check_python_file_by_test_suite(actual_files, test_file, **options) -> float:
211
+ """Check the python file by running the test suite in the given test file."""
212
+
213
+ test_function_name = options.get('test_function_name', 'test')
214
+ # Create a unique module name, it can be arbitrary but must be unique in the current runtime environment
215
+ module_name = 'dynamic_module'
216
+
217
+ # Load the module from the given file path
218
+ spec = importlib.util.spec_from_file_location(module_name, test_file)
219
+ module = importlib.util.module_from_spec(spec)
220
+ sys.modules[module_name] = module # Add the loaded module to sys.modules
221
+ spec.loader.exec_module(module) # Execute the module to make its content available
222
+
223
+ # Retrieve the function by name from the loaded module and execute it
224
+ test_function = getattr(module, test_function_name)
225
+ try:
226
+ if test_function():
227
+ return 1.0
228
+ else:
229
+ return 0.0
230
+ except Exception as e:
231
+ return 0.0
232
+
233
+
234
+ def check_python_file_by_gold_file(actual_files, gold_file: str, **options) -> float:
235
+ pass
236
+
237
+
238
+ def check_html_background_image(src_path: str, rule: Dict = None) -> float:
239
+ """
240
+ Check if the background image is correctly set.
241
+ multi-app:bb7db4c2-30b5-4be7-8dd7-b8c4ec7d3108
242
+ """
243
+ if not src_path:
244
+ return 0.0
245
+
246
+ from bs4 import BeautifulSoup
247
+ with open(src_path, 'r') as f:
248
+ html_content = f.read()
249
+ soup = BeautifulSoup(html_content, 'html.parser')
250
+ styles = soup.find_all('style')
251
+ for style in styles:
252
+ if f'background-image: url(\'{rule["value"]}\')' in style.text:
253
+ return 1.0
254
+ return 0.0
255
+
256
+
257
+ def compare_result_files(src_path, tgt_path):
258
+ """
259
+ Compare whether the content of two files are the same.
260
+ multi-app:7f35355e-02a6-45b5-b140-f0be698bcf85
261
+ """
262
+ if not src_path or not tgt_path:
263
+ return 0.0
264
+
265
+ with open(src_path, 'r') as f:
266
+ src_content = f.read().strip()
267
+ with open(tgt_path, 'r') as f:
268
+ tgt_content = f.read().strip()
269
+ try:
270
+ # Compare the content as numbers
271
+ tgt_content_num = float(tgt_content)
272
+ if tgt_content in src_content:
273
+ # If the content of tgt is in src, return 1.0 since output src might be
274
+ # a superset(language description+number) of tgt
275
+ return 1.0
276
+ src_content_num = float(src_content)
277
+ if abs(src_content_num - tgt_content_num) < 1e-4:
278
+ return 1.0
279
+ return 0.0
280
+ except:
281
+ if src_content == tgt_content:
282
+ return 1.0
283
+ return 0.0
@@ -0,0 +1,35 @@
1
+ from desktop_env.providers.base import VMManager, Provider
2
+
3
+
4
+ def create_vm_manager_and_provider(provider_name: str, region: str, use_proxy: bool = False):
5
+ """
6
+ Factory function to get the Virtual Machine Manager and Provider instances based on the provided provider name.
7
+
8
+ Args:
9
+ provider_name (str): The name of the provider (e.g., "aws", "vmware", etc.)
10
+ region (str): The region for the provider
11
+ use_proxy (bool): Whether to use proxy-enabled providers (currently only supported for AWS)
12
+ """
13
+ provider_name = provider_name.lower().strip()
14
+ if provider_name == "vmware":
15
+ from desktop_env.providers.vmware.manager import VMwareVMManager
16
+ from desktop_env.providers.vmware.provider import VMwareProvider
17
+ return VMwareVMManager(), VMwareProvider(region)
18
+ elif provider_name == "virtualbox":
19
+ from desktop_env.providers.virtualbox.manager import VirtualBoxVMManager
20
+ from desktop_env.providers.virtualbox.provider import VirtualBoxProvider
21
+ return VirtualBoxVMManager(), VirtualBoxProvider(region)
22
+ elif provider_name in ["aws", "amazon web services"]:
23
+ from desktop_env.providers.aws.manager import AWSVMManager
24
+ from desktop_env.providers.aws.provider import AWSProvider
25
+ return AWSVMManager(), AWSProvider(region)
26
+ elif provider_name == "azure":
27
+ from desktop_env.providers.azure.manager import AzureVMManager
28
+ from desktop_env.providers.azure.provider import AzureProvider
29
+ return AzureVMManager(), AzureProvider(region)
30
+ elif provider_name == "docker":
31
+ from desktop_env.providers.docker.manager import DockerVMManager
32
+ from desktop_env.providers.docker.provider import DockerProvider
33
+ return DockerVMManager(), DockerProvider(region)
34
+ else:
35
+ raise NotImplementedError(f"{provider_name} not implemented!")
File without changes
@@ -0,0 +1,278 @@
1
+ import os
2
+ from filelock import FileLock
3
+ import boto3
4
+ import psutil
5
+ import logging
6
+ import dotenv
7
+ import signal
8
+
9
+
10
+ INSTANCE_TYPE = "t3.xlarge"
11
+
12
+ # Load environment variables from .env file
13
+ dotenv.load_dotenv()
14
+
15
+ # Ensure the AWS region is set in the environment
16
+ if not os.getenv('AWS_REGION'):
17
+ raise EnvironmentError("AWS_REGION must be set in the environment variables.")
18
+
19
+ # Ensure the AWS subnet and security group IDs are set in the environment
20
+ if not os.getenv('AWS_SUBNET_ID') or not os.getenv('AWS_SECURITY_GROUP_ID'):
21
+ raise EnvironmentError("AWS_SUBNET_ID and AWS_SECURITY_GROUP_ID must be set in the environment variables.")
22
+
23
+ from desktop_env.providers.base import VMManager
24
+
25
+ # Import proxy-related modules only when needed
26
+ try:
27
+ from desktop_env.providers.aws.proxy_pool import get_global_proxy_pool, init_proxy_pool
28
+ PROXY_SUPPORT_AVAILABLE = True
29
+ except ImportError:
30
+ PROXY_SUPPORT_AVAILABLE = False
31
+
32
+ logger = logging.getLogger("desktopenv.providers.aws.AWSVMManager")
33
+ logger.setLevel(logging.INFO)
34
+
35
+ DEFAULT_REGION = "us-east-1"
36
+ # todo: Add doc for the configuration of image, security group and network interface
37
+ # todo: public the AMI images
38
+ IMAGE_ID_MAP = {
39
+ "us-east-1": {
40
+ (1920, 1080): "ami-09138bff939f82bd8"
41
+ },
42
+ "ap-east-1": {
43
+ (1920, 1080): "ami-0c092a5b8be4116f5"
44
+ }
45
+ }
46
+
47
+
48
+ def _allocate_vm(region=DEFAULT_REGION, screen_size=(1920, 1080)):
49
+
50
+ if region not in IMAGE_ID_MAP:
51
+ raise ValueError(f"Region {region} is not supported. Supported regions are: {list(IMAGE_ID_MAP.keys())}")
52
+ if screen_size not in IMAGE_ID_MAP[region]:
53
+ raise ValueError(f"Screen size {screen_size} not supported for region {region}. Supported: {list(IMAGE_ID_MAP[region].keys())}")
54
+ ami_id = IMAGE_ID_MAP[region][screen_size]
55
+
56
+ ec2_client = boto3.client('ec2', region_name=region)
57
+ instance_id = None
58
+ original_sigint_handler = signal.getsignal(signal.SIGINT)
59
+ original_sigterm_handler = signal.getsignal(signal.SIGTERM)
60
+
61
+ def signal_handler(sig, frame):
62
+ if instance_id:
63
+ signal_name = "SIGINT" if sig == signal.SIGINT else "SIGTERM"
64
+ logger.warning(f"Received {signal_name} signal, terminating instance {instance_id}...")
65
+ try:
66
+ ec2_client.terminate_instances(InstanceIds=[instance_id])
67
+ logger.info(f"Successfully terminated instance {instance_id} after {signal_name}.")
68
+ except Exception as cleanup_error:
69
+ logger.error(f"Failed to terminate instance {instance_id} after {signal_name}: {str(cleanup_error)}")
70
+
71
+ # Restore original signal handlers
72
+ signal.signal(signal.SIGINT, original_sigint_handler)
73
+ signal.signal(signal.SIGTERM, original_sigterm_handler)
74
+
75
+ # Raise appropriate exception based on signal type
76
+ if sig == signal.SIGINT:
77
+ raise KeyboardInterrupt
78
+ else:
79
+ # For SIGTERM, exit gracefully
80
+ import sys
81
+ sys.exit(0)
82
+
83
+ try:
84
+ # Set up signal handlers for both SIGINT and SIGTERM
85
+ signal.signal(signal.SIGINT, signal_handler)
86
+ signal.signal(signal.SIGTERM, signal_handler)
87
+
88
+ if not os.getenv('AWS_SECURITY_GROUP_ID'):
89
+ raise ValueError("AWS_SECURITY_GROUP_ID is not set in the environment variables.")
90
+ if not os.getenv('AWS_SUBNET_ID'):
91
+ raise ValueError("AWS_SUBNET_ID is not set in the environment variables.")
92
+
93
+ run_instances_params = {
94
+ "MaxCount": 1,
95
+ "MinCount": 1,
96
+ "ImageId": ami_id,
97
+ "InstanceType": INSTANCE_TYPE,
98
+ "EbsOptimized": True,
99
+ "NetworkInterfaces": [
100
+ {
101
+ "SubnetId": os.getenv('AWS_SUBNET_ID'),
102
+ "AssociatePublicIpAddress": True,
103
+ "DeviceIndex": 0,
104
+ "Groups": [
105
+ os.getenv('AWS_SECURITY_GROUP_ID')
106
+ ]
107
+ }
108
+ ],
109
+ "BlockDeviceMappings": [
110
+ {
111
+ "DeviceName": "/dev/sda1",
112
+ "Ebs": {
113
+ # "VolumeInitializationRate": 300
114
+ "VolumeSize": 30, # Size in GB
115
+ "VolumeType": "gp3", # General Purpose SSD
116
+ "Throughput": 1000,
117
+ "Iops": 4000 # Adjust IOPS as needed
118
+ }
119
+ }
120
+ ]
121
+ }
122
+
123
+ response = ec2_client.run_instances(**run_instances_params)
124
+ instance_id = response['Instances'][0]['InstanceId']
125
+
126
+ waiter = ec2_client.get_waiter('instance_running')
127
+ logger.info(f"Waiting for instance {instance_id} to be running...")
128
+ waiter.wait(InstanceIds=[instance_id])
129
+ logger.info(f"Instance {instance_id} is ready.")
130
+
131
+ # 获取并显示VNC访问地址
132
+ try:
133
+ instance_details = ec2_client.describe_instances(InstanceIds=[instance_id])
134
+ instance = instance_details['Reservations'][0]['Instances'][0]
135
+ public_ip = instance.get('PublicIpAddress', '')
136
+ if public_ip:
137
+ vnc_url = f"http://{public_ip}:5910/vnc.html"
138
+ logger.info("="*80)
139
+ logger.info(f"🖥️ VNC Web Access URL: {vnc_url}")
140
+ logger.info(f"📡 Public IP: {public_ip}")
141
+ logger.info(f"🆔 Instance ID: {instance_id}")
142
+ logger.info("="*80)
143
+ print(f"\n🌐 VNC访问地址: {vnc_url}")
144
+ print(f"📍 请在浏览器中打开上述地址进行远程桌面访问\n")
145
+ except Exception as e:
146
+ logger.warning(f"Failed to get VNC address for instance {instance_id}: {e}")
147
+ except KeyboardInterrupt:
148
+ logger.warning("VM allocation interrupted by user (SIGINT).")
149
+ if instance_id:
150
+ logger.info(f"Terminating instance {instance_id} due to interruption.")
151
+ ec2_client.terminate_instances(InstanceIds=[instance_id])
152
+ raise
153
+ except Exception as e:
154
+ logger.error(f"Failed to allocate VM: {e}", exc_info=True)
155
+ if instance_id:
156
+ logger.info(f"Terminating instance {instance_id} due to an error.")
157
+ ec2_client.terminate_instances(InstanceIds=[instance_id])
158
+ raise
159
+ finally:
160
+ # Restore original signal handlers
161
+ signal.signal(signal.SIGINT, original_sigint_handler)
162
+ signal.signal(signal.SIGTERM, original_sigterm_handler)
163
+
164
+ return instance_id
165
+
166
+
167
+ def _allocate_vm_with_proxy(region=DEFAULT_REGION, proxy_config_file=None, screen_size=(1920, 1080)):
168
+ """Allocate a VM with proxy configuration"""
169
+ if not PROXY_SUPPORT_AVAILABLE:
170
+ logger.warning("Proxy support not available, falling back to regular VM allocation")
171
+ return _allocate_vm(region, screen_size=screen_size)
172
+
173
+ from desktop_env.providers.aws.provider_with_proxy import AWSProviderWithProxy
174
+
175
+ # Initialize proxy pool if needed
176
+ if proxy_config_file:
177
+ init_proxy_pool(proxy_config_file)
178
+
179
+ # Get current proxy
180
+ proxy_pool = get_global_proxy_pool()
181
+ current_proxy = proxy_pool.get_next_proxy()
182
+
183
+ if current_proxy:
184
+ logger.info(f"Allocating VM with proxy: {current_proxy.host}:{current_proxy.port}")
185
+
186
+ # Create provider instance
187
+ provider = AWSProviderWithProxy(region=region, proxy_config_file=proxy_config_file)
188
+
189
+ # Create new instance
190
+ instance_id = provider.create_instance_with_proxy(
191
+ image_id=IMAGE_ID_MAP[region],
192
+ instance_type=INSTANCE_TYPE,
193
+ security_groups=[os.getenv('AWS_SECURITY_GROUP_ID')],
194
+ subnet_id=os.getenv('AWS_SUBNET_ID')
195
+ )
196
+
197
+ try:
198
+ ec2_client = boto3.client('ec2', region_name=region)
199
+ instance_details = ec2_client.describe_instances(InstanceIds=[instance_id])
200
+ instance = instance_details['Reservations'][0]['Instances'][0]
201
+ public_ip = instance.get('PublicIpAddress', '')
202
+ if public_ip:
203
+ vnc_url = f"http://{public_ip}:5910/vnc.html"
204
+ logger.info("="*80)
205
+ logger.info(f"🖥️ VNC Web Access URL: {vnc_url}")
206
+ logger.info(f"📡 Public IP: {public_ip}")
207
+ logger.info(f"🆔 Instance ID: {instance_id}")
208
+ if current_proxy:
209
+ logger.info(f"🌐 Proxy: {current_proxy.host}:{current_proxy.port}")
210
+ logger.info("="*80)
211
+ print(f"\n🌐 VNC Web Access URL: {vnc_url}")
212
+ if current_proxy:
213
+ print(f"🔄 Current Proxy: {current_proxy.host}:{current_proxy.port}")
214
+ print(f"📍 Please open the above address in the browser for remote desktop access\n")
215
+ except Exception as e:
216
+ logger.warning(f"Failed to get VNC address for proxy instance {instance_id}: {e}")
217
+
218
+ return instance_id
219
+
220
+
221
+ class AWSVMManager(VMManager):
222
+ """
223
+ AWS VM Manager for managing virtual machines on AWS.
224
+
225
+ AWS does not need to maintain a registry of VMs, as it can dynamically allocate and deallocate VMs.
226
+ This class supports both regular VM allocation and proxy-enabled VM allocation.
227
+ """
228
+ def __init__(self, proxy_config_file=None, **kwargs):
229
+ self.proxy_config_file = proxy_config_file
230
+ # self.lock = FileLock(".aws_lck", timeout=60)
231
+ self.initialize_registry()
232
+
233
+ # Initialize proxy pool if proxy configuration is provided
234
+ if proxy_config_file and PROXY_SUPPORT_AVAILABLE:
235
+ init_proxy_pool(proxy_config_file)
236
+ logger.info(f"Proxy pool initialized with config: {proxy_config_file}")
237
+
238
+ def initialize_registry(self, **kwargs):
239
+ pass
240
+
241
+ def add_vm(self, vm_path, region=DEFAULT_REGION, lock_needed=True, **kwargs):
242
+ pass
243
+
244
+ def _add_vm(self, vm_path, region=DEFAULT_REGION):
245
+ pass
246
+
247
+ def delete_vm(self, vm_path, region=DEFAULT_REGION, lock_needed=True, **kwargs):
248
+ pass
249
+
250
+ def _delete_vm(self, vm_path, region=DEFAULT_REGION):
251
+ pass
252
+
253
+ def occupy_vm(self, vm_path, pid, region=DEFAULT_REGION, lock_needed=True, **kwargs):
254
+ pass
255
+
256
+ def _occupy_vm(self, vm_path, pid, region=DEFAULT_REGION):
257
+ pass
258
+
259
+ def check_and_clean(self, lock_needed=True, **kwargs):
260
+ pass
261
+
262
+ def _check_and_clean(self):
263
+ pass
264
+
265
+ def list_free_vms(self, region=DEFAULT_REGION, lock_needed=True, **kwargs):
266
+ pass
267
+
268
+ def _list_free_vms(self, region=DEFAULT_REGION):
269
+ pass
270
+
271
+ def get_vm_path(self, region=DEFAULT_REGION, screen_size=(1920, 1080), **kwargs):
272
+ if self.proxy_config_file:
273
+ logger.info("Allocating a new VM with proxy configuration in region: {}".format(region))
274
+ new_vm_path = _allocate_vm_with_proxy(region, self.proxy_config_file, screen_size=screen_size)
275
+ else:
276
+ logger.info("Allocating a new VM in region: {}".format(region))
277
+ new_vm_path = _allocate_vm(region, screen_size=screen_size)
278
+ return new_vm_path