cua-computer 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (28) hide show
  1. {cua_computer-0.2.1 → cua_computer-0.2.2}/PKG-INFO +2 -2
  2. {cua_computer-0.2.1 → cua_computer-0.2.2}/README.md +1 -1
  3. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/computer.py +67 -58
  4. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/ui/gradio/app.py +43 -13
  5. {cua_computer-0.2.1 → cua_computer-0.2.2}/pyproject.toml +3 -3
  6. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/__init__.py +0 -0
  7. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/interface/__init__.py +0 -0
  8. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/interface/base.py +0 -0
  9. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/interface/factory.py +0 -0
  10. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/interface/linux.py +0 -0
  11. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/interface/macos.py +0 -0
  12. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/interface/models.py +0 -0
  13. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/logger.py +0 -0
  14. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/models.py +0 -0
  15. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/__init__.py +0 -0
  16. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/base.py +0 -0
  17. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/cloud/__init__.py +0 -0
  18. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/cloud/provider.py +0 -0
  19. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/factory.py +0 -0
  20. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/lume/__init__.py +0 -0
  21. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/lume/provider.py +0 -0
  22. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/lume_api.py +0 -0
  23. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/lumier/__init__.py +0 -0
  24. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/providers/lumier/provider.py +0 -0
  25. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/telemetry.py +0 -0
  26. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/ui/__init__.py +0 -0
  27. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/ui/gradio/__init__.py +0 -0
  28. {cua_computer-0.2.1 → cua_computer-0.2.2}/computer/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cua-computer
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Computer-Use Interface (CUI) framework powering Cua
5
5
  Author-Email: TryCua <gh@trycua.com>
6
6
  Requires-Python: >=3.10
@@ -78,7 +78,7 @@ finally:
78
78
  To install the Computer-Use Interface (CUI):
79
79
 
80
80
  ```bash
81
- pip install cua-computer
81
+ pip install "cua-computer[all]"
82
82
  ```
83
83
 
84
84
  The `cua-computer` PyPi package pulls automatically the latest executable version of Lume through [pylume](https://github.com/trycua/pylume).
@@ -54,7 +54,7 @@ finally:
54
54
  To install the Computer-Use Interface (CUI):
55
55
 
56
56
  ```bash
57
- pip install cua-computer
57
+ pip install "cua-computer[all]"
58
58
  ```
59
59
 
60
60
  The `cua-computer` PyPi package pulls automatically the latest executable version of Lume through [pylume](https://github.com/trycua/pylume).
@@ -78,11 +78,11 @@ class Computer:
78
78
  self.provider_type = provider_type
79
79
  self.ephemeral = ephemeral
80
80
 
81
- if ephemeral:
82
- self.storage = "ephemeral"
83
- else:
84
- self.storage = storage
85
-
81
+ # The default is currently to use non-ephemeral storage
82
+ if storage and ephemeral and storage != "ephemeral":
83
+ raise ValueError("Storage path and ephemeral flag cannot be used together")
84
+ self.storage = "ephemeral" if ephemeral else storage
85
+
86
86
  # For Lumier provider, store the first shared directory path to use
87
87
  # for VM file sharing
88
88
  self.shared_path = None
@@ -279,12 +279,14 @@ class Computer:
279
279
  raise RuntimeError(f"Failed to initialize VM provider: {e}")
280
280
 
281
281
  # Check if VM exists or create it
282
+ is_running = False
282
283
  try:
283
284
  if self.config.vm_provider is None:
284
285
  raise RuntimeError(f"VM provider not initialized for {self.config.name}")
285
286
 
286
287
  vm = await self.config.vm_provider.get_vm(self.config.name)
287
288
  self.logger.verbose(f"Found existing VM: {self.config.name}")
289
+ is_running = vm.get("status") == "running"
288
290
  except Exception as e:
289
291
  self.logger.error(f"VM not found: {self.config.name}")
290
292
  self.logger.error(f"Error: {e}")
@@ -292,63 +294,67 @@ class Computer:
292
294
  f"VM {self.config.name} could not be found or created."
293
295
  )
294
296
 
295
- # Convert paths to dictionary format for shared directories
296
- shared_dirs = []
297
- for path in self.shared_directories:
298
- self.logger.verbose(f"Adding shared directory: {path}")
299
- path = os.path.abspath(os.path.expanduser(path))
300
- if os.path.exists(path):
301
- # Add path in format expected by Lume API
302
- shared_dirs.append({
303
- "hostPath": path,
304
- "readOnly": False
305
- })
306
- else:
307
- self.logger.warning(f"Shared directory does not exist: {path}")
297
+ # Start the VM if it's not running
298
+ if not is_running:
299
+ self.logger.info(f"VM {self.config.name} is not running, starting it...")
300
+
301
+ # Convert paths to dictionary format for shared directories
302
+ shared_dirs = []
303
+ for path in self.shared_directories:
304
+ self.logger.verbose(f"Adding shared directory: {path}")
305
+ path = os.path.abspath(os.path.expanduser(path))
306
+ if os.path.exists(path):
307
+ # Add path in format expected by Lume API
308
+ shared_dirs.append({
309
+ "hostPath": path,
310
+ "readOnly": False
311
+ })
312
+ else:
313
+ self.logger.warning(f"Shared directory does not exist: {path}")
314
+
315
+ # Prepare run options to pass to the provider
316
+ run_opts = {}
317
+
318
+ # Add display information if available
319
+ if self.config.display is not None:
320
+ display_info = {
321
+ "width": self.config.display.width,
322
+ "height": self.config.display.height,
323
+ }
308
324
 
309
- # Prepare run options to pass to the provider
310
- run_opts = {}
311
-
312
- # Add display information if available
313
- if self.config.display is not None:
314
- display_info = {
315
- "width": self.config.display.width,
316
- "height": self.config.display.height,
317
- }
318
-
319
- # Check if scale_factor exists before adding it
320
- if hasattr(self.config.display, "scale_factor"):
321
- display_info["scale_factor"] = self.config.display.scale_factor
322
-
323
- run_opts["display"] = display_info
325
+ # Check if scale_factor exists before adding it
326
+ if hasattr(self.config.display, "scale_factor"):
327
+ display_info["scale_factor"] = self.config.display.scale_factor
328
+
329
+ run_opts["display"] = display_info
324
330
 
325
- # Add shared directories if available
326
- if self.shared_directories:
327
- run_opts["shared_directories"] = shared_dirs.copy()
331
+ # Add shared directories if available
332
+ if self.shared_directories:
333
+ run_opts["shared_directories"] = shared_dirs.copy()
328
334
 
329
- # Run the VM with the provider
330
- try:
331
- if self.config.vm_provider is None:
332
- raise RuntimeError(f"VM provider not initialized for {self.config.name}")
335
+ # Run the VM with the provider
336
+ try:
337
+ if self.config.vm_provider is None:
338
+ raise RuntimeError(f"VM provider not initialized for {self.config.name}")
339
+
340
+ # Use the complete run_opts we prepared earlier
341
+ # Handle ephemeral storage for run_vm method too
342
+ storage_param = "ephemeral" if self.ephemeral else self.storage
333
343
 
334
- # Use the complete run_opts we prepared earlier
335
- # Handle ephemeral storage for run_vm method too
336
- storage_param = "ephemeral" if self.ephemeral else self.storage
337
-
338
- # Log the image being used
339
- self.logger.info(f"Running VM using image: {self.image}")
340
-
341
- # Call provider.run_vm with explicit image parameter
342
- response = await self.config.vm_provider.run_vm(
343
- image=self.image,
344
- name=self.config.name,
345
- run_opts=run_opts,
346
- storage=storage_param
347
- )
348
- self.logger.info(f"VM run response: {response if response else 'None'}")
349
- except Exception as run_error:
350
- self.logger.error(f"Failed to run VM: {run_error}")
351
- raise RuntimeError(f"Failed to start VM: {run_error}")
344
+ # Log the image being used
345
+ self.logger.info(f"Running VM using image: {self.image}")
346
+
347
+ # Call provider.run_vm with explicit image parameter
348
+ response = await self.config.vm_provider.run_vm(
349
+ image=self.image,
350
+ name=self.config.name,
351
+ run_opts=run_opts,
352
+ storage=storage_param
353
+ )
354
+ self.logger.info(f"VM run response: {response if response else 'None'}")
355
+ except Exception as run_error:
356
+ self.logger.error(f"Failed to run VM: {run_error}")
357
+ raise RuntimeError(f"Failed to start VM: {run_error}")
352
358
 
353
359
  # Wait for VM to be ready with a valid IP address
354
360
  self.logger.info("Waiting for VM to be ready with a valid IP address...")
@@ -406,6 +412,9 @@ class Computer:
406
412
  raise TimeoutError(
407
413
  f"Could not connect to WebSocket interface at {ip_address}:8000/ws: {str(e)}"
408
414
  )
415
+ # self.logger.warning(
416
+ # f"Could not connect to WebSocket interface at {ip_address}:8000/ws: {str(e)}, expect missing functionality"
417
+ # )
409
418
 
410
419
  # Create an event to keep the VM running in background if needed
411
420
  if not self.use_host_computer_server:
@@ -17,7 +17,7 @@ import base64
17
17
  from datetime import datetime
18
18
  from PIL import Image
19
19
  from huggingface_hub import DatasetCard, DatasetCardData
20
- from computer import Computer
20
+ from computer import Computer, VMProviderType
21
21
  from gradio.components import ChatMessage
22
22
  import pandas as pd
23
23
  from datasets import Dataset, Features, Sequence, concatenate_datasets
@@ -528,21 +528,44 @@ async def execute(name, action, arguments):
528
528
 
529
529
  return results
530
530
 
531
- async def handle_init_computer():
532
- """Initialize the computer instance and tools"""
531
+ async def handle_init_computer(os_choice: str):
532
+ """Initialize the computer instance and tools for macOS or Ubuntu"""
533
533
  global computer, tool_call_logs, tools
534
-
535
- computer = Computer(os_type="macos", display="1024x768", memory="8GB", cpu="4")
534
+
535
+ if os_choice == "Ubuntu":
536
+ computer = Computer(
537
+ image="ubuntu-noble-vanilla:latest",
538
+ os_type="linux",
539
+ provider_type=VMProviderType.LUME,
540
+ display="1024x768",
541
+ memory="8GB",
542
+ cpu="4"
543
+ )
544
+ os_type_str = "linux"
545
+ image_str = "ubuntu-noble-vanilla:latest"
546
+ else:
547
+ computer = Computer(
548
+ image="macos-sequoia-cua:latest",
549
+ os_type="macos",
550
+ provider_type=VMProviderType.LUME,
551
+ display="1024x768",
552
+ memory="8GB",
553
+ cpu="4"
554
+ )
555
+ os_type_str = "macos"
556
+ image_str = "macos-sequoia-cua:latest"
557
+
536
558
  await computer.run()
537
-
559
+
538
560
  # Log computer initialization as a tool call
539
561
  result = await execute("computer", "initialize", {
540
- "os": "macos",
541
- "display": "1024x768",
542
- "memory": "8GB",
562
+ "os": os_type_str,
563
+ "image": image_str,
564
+ "display": "1024x768",
565
+ "memory": "8GB",
543
566
  "cpu": "4"
544
567
  })
545
-
568
+
546
569
  return result["screenshot"], json.dumps(tool_call_logs, indent=2)
547
570
 
548
571
  async def handle_screenshot():
@@ -1004,8 +1027,15 @@ def create_gradio_ui():
1004
1027
  run_setup_btn = gr.Button("⚙️ Run Task Setup")
1005
1028
  # Setup status textbox
1006
1029
  setup_status = gr.Textbox(label="Setup Status", value="")
1007
-
1008
- start_btn = gr.Button("Initialize Computer")
1030
+
1031
+ with gr.Group():
1032
+ os_choice = gr.Radio(
1033
+ label="OS",
1034
+ choices=["macOS", "Ubuntu"],
1035
+ value="macOS",
1036
+ interactive=False # disable until the ubuntu image is ready
1037
+ )
1038
+ start_btn = gr.Button("Initialize Computer")
1009
1039
 
1010
1040
  with gr.Group():
1011
1041
  input_text = gr.Textbox(label="Type Text")
@@ -1169,7 +1199,7 @@ def create_gradio_ui():
1169
1199
  )
1170
1200
 
1171
1201
  img.select(handle_click, inputs=[img, click_type], outputs=[img, action_log])
1172
- start_btn.click(handle_init_computer, outputs=[img, action_log])
1202
+ start_btn.click(handle_init_computer, inputs=[os_choice], outputs=[img, action_log])
1173
1203
  wait_btn.click(handle_wait, outputs=[img, action_log])
1174
1204
 
1175
1205
  # DONE and FAIL buttons just do a placeholder action
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "cua-computer"
9
- version = "0.2.1"
9
+ version = "0.2.2"
10
10
  description = "Computer-Use Interface (CUI) framework powering Cua"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -57,7 +57,7 @@ target-version = [
57
57
 
58
58
  [tool.ruff]
59
59
  line-length = 100
60
- target-version = "0.2.1"
60
+ target-version = "0.2.2"
61
61
  select = [
62
62
  "E",
63
63
  "F",
@@ -71,7 +71,7 @@ docstring-code-format = true
71
71
 
72
72
  [tool.mypy]
73
73
  strict = true
74
- python_version = "0.2.1"
74
+ python_version = "0.2.2"
75
75
  ignore_missing_imports = true
76
76
  disallow_untyped_defs = true
77
77
  check_untyped_defs = true