mirror of
https://github.com/trycua/lume.git
synced 2026-01-07 04:50:03 -06:00
Merge branch 'main' into feat/cua-bench-submodules
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.4.11
|
||||
current_version = 0.4.17
|
||||
commit = True
|
||||
tag = True
|
||||
tag_name = computer-v{new_version}
|
||||
|
||||
@@ -40,7 +40,7 @@ try:
|
||||
await computer.interface.right_click(300, 300)
|
||||
await computer.interface.double_click(400, 400)
|
||||
|
||||
await computer.interface.type("Hello, World!")
|
||||
await computer.interface.type_text("Hello, World!")
|
||||
await computer.interface.press_key("enter")
|
||||
|
||||
await computer.interface.set_clipboard("Test clipboard")
|
||||
|
||||
@@ -107,13 +107,17 @@ class Computer:
|
||||
host: Host to use for VM provider connections (e.g. "localhost", "host.docker.internal")
|
||||
storage: Optional path for persistent VM storage (Lumier provider)
|
||||
ephemeral: Whether to use ephemeral storage
|
||||
api_key: Optional API key for cloud providers
|
||||
api_key: Optional API key for cloud providers (defaults to CUA_API_KEY environment variable)
|
||||
experiments: Optional list of experimental features to enable (e.g. ["app-use"])
|
||||
"""
|
||||
|
||||
self.logger = Logger("computer", verbosity)
|
||||
self.logger.info("Initializing Computer...")
|
||||
|
||||
# Fall back to environment variable for api_key if not provided
|
||||
if api_key is None:
|
||||
api_key = os.environ.get("CUA_API_KEY")
|
||||
|
||||
if not image:
|
||||
if os_type == "macos":
|
||||
image = "macos-sequoia-cua:latest"
|
||||
|
||||
@@ -31,21 +31,26 @@ class CloudProvider(BaseVMProvider):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api_key: str,
|
||||
api_key: Optional[str] = None,
|
||||
verbose: bool = False,
|
||||
api_base: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
api_key: API key for authentication
|
||||
api_key: API key for authentication (defaults to CUA_API_KEY environment variable)
|
||||
name: Name of the VM
|
||||
verbose: Enable verbose logging
|
||||
"""
|
||||
assert api_key, "api_key required for CloudProvider"
|
||||
# Fall back to environment variable if api_key not provided
|
||||
if api_key is None:
|
||||
api_key = os.getenv("CUA_API_KEY")
|
||||
assert api_key, "api_key required for CloudProvider (provide via parameter or CUA_API_KEY environment variable)"
|
||||
self.api_key = api_key
|
||||
self.verbose = verbose
|
||||
self.api_base = (api_base or DEFAULT_API_BASE).rstrip("/")
|
||||
# Host caching dictionary: {vm_name: host_string}
|
||||
self._host_cache: Dict[str, str] = {}
|
||||
|
||||
@property
|
||||
def provider_type(self) -> VMProviderType:
|
||||
@@ -60,12 +65,12 @@ class CloudProvider(BaseVMProvider):
|
||||
async def get_vm(self, name: str, storage: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Get VM information by querying the VM status endpoint.
|
||||
|
||||
- Build hostname via get_ip(name) → "{name}.containers.cloud.trycua.com"
|
||||
- Build hostname via _get_host_for_vm(name) using cached host or fallback
|
||||
- Probe https://{hostname}:8443/status with a short timeout
|
||||
- If JSON contains a "status" field, return it; otherwise infer
|
||||
- Fallback to DNS resolve check to distinguish unknown vs not_found
|
||||
"""
|
||||
hostname = await self.get_ip(name=name)
|
||||
hostname = await self._get_host_for_vm(name)
|
||||
|
||||
# Try HTTPS probe to the computer-server status endpoint (8443)
|
||||
try:
|
||||
@@ -118,8 +123,20 @@ class CloudProvider(BaseVMProvider):
|
||||
vm = dict(item) if isinstance(item, dict) else {}
|
||||
name = vm.get("name")
|
||||
password = vm.get("password")
|
||||
api_host = vm.get("host") # Read host from API response
|
||||
|
||||
if isinstance(name, str) and name:
|
||||
host = f"{name}.containers.cloud.trycua.com"
|
||||
# Use host from API if available, otherwise fallback to legacy format
|
||||
if isinstance(api_host, str) and api_host:
|
||||
host = api_host
|
||||
# Cache the host for this VM
|
||||
self._host_cache[name] = host
|
||||
else:
|
||||
# Legacy fallback
|
||||
host = f"{name}.containers.cloud.trycua.com"
|
||||
# Cache the legacy host
|
||||
self._host_cache[name] = host
|
||||
|
||||
# api_url: always set if missing
|
||||
if not vm.get("api_url"):
|
||||
vm["api_url"] = f"https://{host}:8443"
|
||||
@@ -227,15 +244,73 @@ class CloudProvider(BaseVMProvider):
|
||||
"message": "update_vm not supported by public API",
|
||||
}
|
||||
|
||||
async def _get_host_for_vm(self, name: str) -> str:
|
||||
"""
|
||||
Get the host for a VM, trying multiple approaches:
|
||||
1. Check cache first
|
||||
2. Try to refresh cache by calling list_vms
|
||||
3. Try .sandbox.cua.ai format
|
||||
4. Fallback to legacy .containers.cloud.trycua.com format
|
||||
|
||||
Args:
|
||||
name: VM name
|
||||
|
||||
Returns:
|
||||
Host string for the VM
|
||||
"""
|
||||
# Check cache first
|
||||
if name in self._host_cache:
|
||||
return self._host_cache[name]
|
||||
|
||||
# Try to refresh cache by calling list_vms
|
||||
try:
|
||||
await self.list_vms()
|
||||
# Check cache again after refresh
|
||||
if name in self._host_cache:
|
||||
return self._host_cache[name]
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to refresh VM list for host lookup: {e}")
|
||||
|
||||
# Try .sandbox.cua.ai format first
|
||||
sandbox_host = f"{name}.sandbox.cua.ai"
|
||||
if await self._test_host_connectivity(sandbox_host):
|
||||
self._host_cache[name] = sandbox_host
|
||||
return sandbox_host
|
||||
|
||||
# Fallback to legacy format
|
||||
legacy_host = f"{name}.containers.cloud.trycua.com"
|
||||
# Cache the legacy host
|
||||
self._host_cache[name] = legacy_host
|
||||
return legacy_host
|
||||
|
||||
async def _test_host_connectivity(self, hostname: str) -> bool:
|
||||
"""
|
||||
Test if a host is reachable by trying to connect to its status endpoint.
|
||||
|
||||
Args:
|
||||
hostname: Host to test
|
||||
|
||||
Returns:
|
||||
True if host is reachable, False otherwise
|
||||
"""
|
||||
try:
|
||||
timeout = aiohttp.ClientTimeout(total=2) # Short timeout for connectivity test
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
url = f"https://{hostname}:8443/status"
|
||||
async with session.get(url, allow_redirects=False) as resp:
|
||||
# Any response (even error) means the host is reachable
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def get_ip(
|
||||
self, name: Optional[str] = None, storage: Optional[str] = None, retry_delay: int = 2
|
||||
) -> str:
|
||||
"""
|
||||
Return the VM's IP address as '{container_name}.containers.cloud.trycua.com'.
|
||||
Uses the provided 'name' argument (the VM name requested by the caller),
|
||||
falling back to self.name only if 'name' is None.
|
||||
Retries up to 3 times with retry_delay seconds if hostname is not available.
|
||||
Return the VM's host address, trying to use cached host from API or falling back to legacy format.
|
||||
Uses the provided 'name' argument (the VM name requested by the caller).
|
||||
"""
|
||||
if name is None:
|
||||
raise ValueError("VM name is required for CloudProvider.get_ip")
|
||||
return f"{name}.containers.cloud.trycua.com"
|
||||
|
||||
return await self._get_host_for_vm(name)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "pdm.backend"
|
||||
|
||||
[project]
|
||||
name = "cua-computer"
|
||||
version = "0.4.11"
|
||||
version = "0.4.17"
|
||||
description = "Computer-Use Interface (CUI) framework powering Cua"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
||||
Reference in New Issue
Block a user