From 66c0d7f1f1dbd1043055468a98ed1abbe2787302 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 14 Aug 2025 12:58:51 -0400 Subject: [PATCH 01/31] Added agent proxy --- libs/python/agent/agent/proxy/README.md | 237 ++++++++++++++++++++ libs/python/agent/agent/proxy/__init__.py | 8 + libs/python/agent/agent/proxy/cli.py | 21 ++ libs/python/agent/agent/proxy/examples.py | 177 +++++++++++++++ libs/python/agent/agent/proxy/handlers.py | 160 +++++++++++++ libs/python/agent/agent/proxy/p2p_server.py | 185 +++++++++++++++ libs/python/agent/agent/proxy/server.py | 175 +++++++++++++++ 7 files changed, 963 insertions(+) create mode 100644 libs/python/agent/agent/proxy/README.md create mode 100644 libs/python/agent/agent/proxy/__init__.py create mode 100644 libs/python/agent/agent/proxy/cli.py create mode 100644 libs/python/agent/agent/proxy/examples.py create mode 100644 libs/python/agent/agent/proxy/handlers.py create mode 100644 libs/python/agent/agent/proxy/p2p_server.py create mode 100644 libs/python/agent/agent/proxy/server.py diff --git a/libs/python/agent/agent/proxy/README.md b/libs/python/agent/agent/proxy/README.md new file mode 100644 index 00000000..9379ebc2 --- /dev/null +++ b/libs/python/agent/agent/proxy/README.md @@ -0,0 +1,237 @@ +# ComputerAgent Proxy + +A proxy server that exposes ComputerAgent functionality over HTTP and P2P (WebRTC) connections. This allows remote clients to interact with ComputerAgent instances through a simple REST-like API. + +## Features + +- **HTTP Server**: Standard HTTP REST API using Starlette +- **P2P Server**: WebRTC-based peer-to-peer connections using peerjs-python +- **No Pydantic**: Uses plain dictionaries for request/response handling +- **OpenAI-compatible API**: Similar request format to OpenAI's API +- **Multi-modal Support**: Handles text and image inputs +- **Configurable**: Supports custom agent and computer configurations + +## Installation + +The proxy requires additional dependencies: + +```bash +# For HTTP server +pip install starlette uvicorn + +# For P2P server (optional) +pip install peerjs-python aiortc +``` + +## Usage + +### Starting the Server + +```bash +# HTTP server only (default) +python -m agent.proxy.cli + +# Custom host/port +python -m agent.proxy.cli --host 0.0.0.0 --port 8080 + +# P2P server only +python -m agent.proxy.cli --mode p2p --peer-id my-agent-proxy + +# Both HTTP and P2P +python -m agent.proxy.cli --mode both --peer-id my-agent-proxy +``` + +### API Endpoints + +#### POST /responses + +Process a request using ComputerAgent and return the first result. + +**Request Format:** +```json +{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Your instruction here", + "agent_kwargs": { + "save_trajectory": true, + "verbosity": 20 + }, + "computer_kwargs": { + "os_type": "linux", + "provider_type": "cloud" + } +} +``` + +**Multi-modal Request:** +```json +{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": [ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "what is in this image?"}, + { + "type": "input_image", + "image_url": "https://example.com/image.jpg" + } + ] + } + ] +} +``` + +**Response Format:** +```json +{ + "success": true, + "result": { + // Agent response data + }, + "model": "anthropic/claude-3-5-sonnet-20241022" +} +``` + +#### GET /health + +Health check endpoint. + +### cURL Examples + +```bash +# Simple text request +curl http://localhost:8000/responses \ + -H "Content-Type: application/json" \ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Tell me a three sentence bedtime story about a unicorn." + }' + +# Multi-modal request +curl http://localhost:8000/responses \ + -H "Content-Type: application/json" \ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": [ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "what is in this image?"}, + { + "type": "input_image", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" + } + ] + } + ] + }' +``` + +### P2P Usage + +The P2P server allows WebRTC connections for direct peer-to-peer communication: + +```python +# Connect to P2P proxy +from peerjs import Peer, PeerOptions +import json + +peer = Peer(id="client", peer_options=PeerOptions(host="localhost", port=9000)) +await peer.start() + +connection = peer.connect("computer-agent-proxy") + +# Send request +request = { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Hello from P2P!" +} +await connection.send(json.dumps(request)) +``` + +## Configuration + +### Environment Variables + +- `CUA_CONTAINER_NAME`: Default container name for cloud provider +- `CUA_API_KEY`: Default API key for cloud provider + +### Request Parameters + +- `model`: Model string (required) - e.g., "anthropic/claude-3-5-sonnet-20241022" +- `input`: String or message array (required) +- `agent_kwargs`: Optional agent configuration +- `computer_kwargs`: Optional computer configuration + +### Agent Configuration (`agent_kwargs`) + +Common options: +- `save_trajectory`: Boolean - Save conversation trajectory +- `verbosity`: Integer - Logging level (10=DEBUG, 20=INFO, etc.) +- `max_trajectory_budget`: Float - Budget limit for trajectory + +### Computer Configuration (`computer_kwargs`) + +Common options: +- `os_type`: String - "linux", "windows", "macos" +- `provider_type`: String - "cloud", "local", "docker" +- `name`: String - Instance name +- `api_key`: String - Provider API key + +## Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ HTTP Client │ │ P2P Client │ │ Direct Usage │ +└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ProxyServer │ +├─────────────────────────────────────────────────────────────────┤ +│ ResponsesHandler │ +├─────────────────────────────────────────────────────────────────┤ +│ ComputerAgent + Computer │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Examples + +See `examples.py` for complete usage examples: + +```bash +# Run HTTP tests +python agent/proxy/examples.py + +# Show curl examples +python agent/proxy/examples.py curl + +# Test P2P (requires peerjs-python) +python agent/proxy/examples.py p2p +``` + +## Error Handling + +The proxy returns structured error responses: + +```json +{ + "success": false, + "error": "Error description", + "model": "model-used" +} +``` + +Common errors: +- Missing required parameters (`model`, `input`) +- Invalid JSON in request body +- Agent execution errors +- Computer setup failures + +## Limitations + +- Returns only the first result from agent.run() (as requested) +- P2P requires peerjs-python and signaling server +- Computer instances are created per request (not pooled) +- No authentication/authorization built-in diff --git a/libs/python/agent/agent/proxy/__init__.py b/libs/python/agent/agent/proxy/__init__.py new file mode 100644 index 00000000..294f5773 --- /dev/null +++ b/libs/python/agent/agent/proxy/__init__.py @@ -0,0 +1,8 @@ +""" +Proxy module for exposing ComputerAgent over HTTP and P2P connections. +""" + +from .server import ProxyServer +from .handlers import ResponsesHandler + +__all__ = ['ProxyServer', 'ResponsesHandler'] diff --git a/libs/python/agent/agent/proxy/cli.py b/libs/python/agent/agent/proxy/cli.py new file mode 100644 index 00000000..5945320b --- /dev/null +++ b/libs/python/agent/agent/proxy/cli.py @@ -0,0 +1,21 @@ +""" +CLI entry point for the proxy server. +""" + +import asyncio +import sys +from .server import main + +def cli(): + """CLI entry point.""" + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\nShutting down...") + sys.exit(0) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + +if __name__ == "__main__": + cli() diff --git a/libs/python/agent/agent/proxy/examples.py b/libs/python/agent/agent/proxy/examples.py new file mode 100644 index 00000000..238fc77a --- /dev/null +++ b/libs/python/agent/agent/proxy/examples.py @@ -0,0 +1,177 @@ +""" +Example usage of the proxy server and client requests. +""" + +import asyncio +import json +import aiohttp +from typing import Dict, Any + + +async def test_http_endpoint(): + """Test the HTTP /responses endpoint.""" + + # Example 1: Simple text request + simple_request = { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Tell me a three sentence bedtime story about a unicorn." + } + + # Example 2: Multi-modal request with image + multimodal_request = { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": [ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "what is in this image?"}, + { + "type": "input_image", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" + } + ] + } + ] + } + + # Example 3: Request with custom agent and computer kwargs + custom_request = { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Take a screenshot and tell me what you see", + "agent_kwargs": { + "save_trajectory": True, + "verbosity": 20 # INFO level + }, + "computer_kwargs": { + "os_type": "linux", + "provider_type": "cloud" + } + } + + # Test requests + base_url = "http://localhost:8000" + + async with aiohttp.ClientSession() as session: + for i, request_data in enumerate([simple_request, multimodal_request, custom_request], 1): + print(f"\n--- Test {i} ---") + print(f"Request: {json.dumps(request_data, indent=2)}") + + try: + async with session.post( + f"{base_url}/responses", + json=request_data, + headers={"Content-Type": "application/json"} + ) as response: + result = await response.json() + print(f"Status: {response.status}") + print(f"Response: {json.dumps(result, indent=2)}") + + except Exception as e: + print(f"Error: {e}") + + +def curl_examples(): + """Print curl command examples.""" + + print("=== CURL Examples ===\n") + + print("1. Simple text request:") + print("""curl http://localhost:8000/responses \\ + -H "Content-Type: application/json" \\ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Tell me a three sentence bedtime story about a unicorn." + }'""") + + print("\n2. Multi-modal request with image:") + print("""curl http://localhost:8000/responses \\ + -H "Content-Type: application/json" \\ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": [ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "what is in this image?"}, + { + "type": "input_image", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" + } + ] + } + ] + }'""") + + print("\n3. Request with custom configuration:") + print("""curl http://localhost:8000/responses \\ + -H "Content-Type: application/json" \\ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Take a screenshot and tell me what you see", + "agent_kwargs": { + "save_trajectory": true, + "verbosity": 20 + }, + "computer_kwargs": { + "os_type": "linux", + "provider_type": "cloud" + } + }'""") + + +async def test_p2p_client(): + """Example P2P client using peerjs-python.""" + try: + from peerjs import Peer, PeerOptions, ConnectionEventType + from aiortc import RTCConfiguration, RTCIceServer + + # Set up client peer + options = PeerOptions( + host="0.peerjs.com", + port=443, + secure=True, + config=RTCConfiguration( + iceServers=[RTCIceServer(urls="stun:stun.l.google.com:19302")] + ) + ) + + client_peer = Peer(id="test-client", peer_options=options) + await client_peer.start() + + # Connect to proxy server + connection = client_peer.connect("computer-agent-proxy") + + @connection.on(ConnectionEventType.Open) + async def connection_open(): + print("Connected to proxy server") + + # Send a test request + request = { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Hello from P2P client!" + } + await connection.send(json.dumps(request)) + + @connection.on(ConnectionEventType.Data) + async def connection_data(data): + print(f"Received response: {data}") + await client_peer.destroy() + + # Wait for connection + await asyncio.sleep(10) + + except ImportError: + print("P2P dependencies not available. Install peerjs-python for P2P testing.") + except Exception as e: + print(f"P2P test error: {e}") + + +if __name__ == "__main__": + import sys + + if len(sys.argv) > 1 and sys.argv[1] == "curl": + curl_examples() + elif len(sys.argv) > 1 and sys.argv[1] == "p2p": + asyncio.run(test_p2p_client()) + else: + asyncio.run(test_http_endpoint()) diff --git a/libs/python/agent/agent/proxy/handlers.py b/libs/python/agent/agent/proxy/handlers.py new file mode 100644 index 00000000..f74bf0c4 --- /dev/null +++ b/libs/python/agent/agent/proxy/handlers.py @@ -0,0 +1,160 @@ +""" +Request handlers for the proxy endpoints. +""" + +import asyncio +import json +import logging +import os +from typing import Dict, Any, List, Union, Optional + +from ..agent import ComputerAgent +from computer import Computer + +logger = logging.getLogger(__name__) + + +class ResponsesHandler: + """Handler for /responses endpoint that processes agent requests.""" + + def __init__(self): + self.computer = None + self.agent = None + + async def setup_computer(self, computer_kwargs: Optional[Dict[str, Any]] = None): + """Set up computer instance with provided kwargs or defaults.""" + if self.computer is not None: + return # Already set up + + # Default computer configuration + default_config = { + "os_type": "linux", + "provider_type": "cloud", + "name": os.getenv("CUA_CONTAINER_NAME"), + "api_key": os.getenv("CUA_API_KEY") + } + + # Override with provided kwargs + if computer_kwargs: + default_config.update(computer_kwargs) + + self.computer = Computer(**default_config) + await self.computer.__aenter__() + logger.info(f"Computer set up with config: {default_config}") + + async def setup_agent(self, model: str, agent_kwargs: Optional[Dict[str, Any]] = None): + """Set up agent instance with provided model and kwargs.""" + if self.computer is None: + raise RuntimeError("Computer must be set up before agent") + + # Default agent configuration + default_config = { + "model": model, + "tools": [self.computer] + } + + # Override with provided kwargs + if agent_kwargs: + # Don't override tools unless explicitly provided + if "tools" not in agent_kwargs: + agent_kwargs["tools"] = [self.computer] + default_config.update(agent_kwargs) + + self.agent = ComputerAgent(**default_config) + logger.info(f"Agent set up with model: {model}") + + async def process_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]: + """ + Process a /responses request and return the result. + + Args: + request_data: Dictionary containing model, input, and optional kwargs + + Returns: + Dictionary with the agent's response + """ + try: + # Extract request parameters + model = request_data.get("model") + input_data = request_data.get("input") + agent_kwargs = request_data.get("agent_kwargs", {}) + computer_kwargs = request_data.get("computer_kwargs", {}) + + if not model: + raise ValueError("Model is required") + if not input_data: + raise ValueError("Input is required") + + # Set up computer and agent + await self.setup_computer(computer_kwargs) + await self.setup_agent(model, agent_kwargs) + + # Convert input to messages format + messages = self._convert_input_to_messages(input_data) + + # Run agent and get first result + async for result in self.agent.run(messages): + # Return the first result and break + return { + "success": True, + "result": result, + "model": model + } + + # If no results were yielded + return { + "success": False, + "error": "No results from agent", + "model": model + } + + except Exception as e: + logger.error(f"Error processing request: {e}") + return { + "success": False, + "error": str(e), + "model": request_data.get("model", "unknown") + } + + def _convert_input_to_messages(self, input_data: Union[str, List[Dict[str, Any]]]) -> List[Dict[str, Any]]: + """Convert input data to messages format.""" + if isinstance(input_data, str): + # Simple string input + return [{"role": "user", "content": input_data}] + elif isinstance(input_data, list): + # Already in messages format + messages = [] + for msg in input_data: + # Convert content array format if needed + if isinstance(msg.get("content"), list): + content_parts = [] + for part in msg["content"]: + if part.get("type") == "input_text": + content_parts.append({"type": "text", "text": part["text"]}) + elif part.get("type") == "input_image": + content_parts.append({ + "type": "image_url", + "image_url": {"url": part["image_url"]} + }) + else: + content_parts.append(part) + messages.append({ + "role": msg["role"], + "content": content_parts + }) + else: + messages.append(msg) + return messages + else: + raise ValueError("Input must be string or list of messages") + + async def cleanup(self): + """Clean up resources.""" + if self.computer: + try: + await self.computer.__aexit__(None, None, None) + except Exception as e: + logger.error(f"Error cleaning up computer: {e}") + finally: + self.computer = None + self.agent = None diff --git a/libs/python/agent/agent/proxy/p2p_server.py b/libs/python/agent/agent/proxy/p2p_server.py new file mode 100644 index 00000000..ed50f4ba --- /dev/null +++ b/libs/python/agent/agent/proxy/p2p_server.py @@ -0,0 +1,185 @@ +""" +P2P server implementation using peerjs-python for WebRTC connections. +""" + +import asyncio +import json +import logging +from typing import Dict, Any, Optional + +logger = logging.getLogger(__name__) + + +class P2PServer: + """P2P server using peerjs-python for WebRTC connections.""" + + def __init__(self, handler, peer_id: Optional[str] = None, signaling_server: Optional[Dict[str, Any]] = None): + self.handler = handler + self.peer_id = peer_id or "computer-agent-proxy" + self.signaling_server = signaling_server or { + "host": "0.peerjs.com", + "port": 443, + "secure": True + } + self.peer = None + + async def start(self): + """Start P2P server with WebRTC connections.""" + try: + from peerjs import Peer, PeerOptions, PeerEventType, ConnectionEventType + from aiortc import RTCConfiguration, RTCIceServer + + # Set up peer options + ice_servers = [ + {"urls": "stun:stun.l.google.com:19302"}, + {"urls": "stun:stun1.l.google.com:19302"} + ] + + options = PeerOptions( + host=self.signaling_server["host"], + port=self.signaling_server["port"], + secure=self.signaling_server["secure"], + config=RTCConfiguration( + iceServers=[RTCIceServer(**srv) for srv in ice_servers] + ) + ) + + # Create peer + self.peer = Peer(id=self.peer_id, peer_options=options) + await self.peer.start() + logger.info(f"P2P peer started with ID: {self.peer_id}") + + # Set up connection handlers + @self.peer.on(PeerEventType.Connection) + async def peer_connection(peer_connection): + logger.info(f"Remote peer {peer_connection.peer} trying to establish connection") + await self._setup_connection_handlers(peer_connection) + + @self.peer.on(PeerEventType.Error) + async def peer_error(error): + logger.error(f"Peer error: {error}") + + # Keep the server running + while True: + await asyncio.sleep(1) + + except ImportError as e: + logger.error(f"P2P dependencies not available: {e}") + logger.error("Install peerjs-python: pip install peerjs-python") + raise + except Exception as e: + logger.error(f"Error starting P2P server: {e}") + raise + + async def _setup_connection_handlers(self, peer_connection): + """Set up handlers for a peer connection.""" + try: + from peerjs import ConnectionEventType + + @peer_connection.on(ConnectionEventType.Open) + async def connection_open(): + logger.info(f"Connection opened with peer {peer_connection.peer}") + + # Send welcome message + welcome_msg = { + "type": "welcome", + "message": "Connected to ComputerAgent Proxy", + "endpoints": ["/responses"] + } + await peer_connection.send(json.dumps(welcome_msg)) + + @peer_connection.on(ConnectionEventType.Data) + async def connection_data(data): + logger.debug(f"Data received from peer {peer_connection.peer}: {data}") + + try: + # Parse the incoming data + if isinstance(data, str): + request_data = json.loads(data) + else: + request_data = data + + # Check if it's an HTTP-like request + if self._is_http_request(request_data): + response = await self._handle_http_request(request_data) + await peer_connection.send(json.dumps(response)) + else: + # Direct API request + result = await self.handler.process_request(request_data) + await peer_connection.send(json.dumps(result)) + + except json.JSONDecodeError: + error_response = { + "success": False, + "error": "Invalid JSON in request" + } + await peer_connection.send(json.dumps(error_response)) + except Exception as e: + logger.error(f"Error processing P2P request: {e}") + error_response = { + "success": False, + "error": str(e) + } + await peer_connection.send(json.dumps(error_response)) + + @peer_connection.on(ConnectionEventType.Close) + async def connection_close(): + logger.info(f"Connection closed with peer {peer_connection.peer}") + + @peer_connection.on(ConnectionEventType.Error) + async def connection_error(error): + logger.error(f"Connection error with peer {peer_connection.peer}: {error}") + + except Exception as e: + logger.error(f"Error setting up connection handlers: {e}") + + def _is_http_request(self, data: Dict[str, Any]) -> bool: + """Check if the data looks like an HTTP request.""" + return ( + isinstance(data, dict) and + "method" in data and + "path" in data and + data.get("method") == "POST" and + data.get("path") == "/responses" + ) + + async def _handle_http_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]: + """Handle HTTP-like request over P2P.""" + try: + method = request_data.get("method") + path = request_data.get("path") + body = request_data.get("body", {}) + + if method == "POST" and path == "/responses": + # Process the request body + result = await self.handler.process_request(body) + return { + "status": 200, + "headers": {"Content-Type": "application/json"}, + "body": result + } + else: + return { + "status": 404, + "headers": {"Content-Type": "application/json"}, + "body": {"success": False, "error": "Endpoint not found"} + } + + except Exception as e: + logger.error(f"Error handling HTTP request: {e}") + return { + "status": 500, + "headers": {"Content-Type": "application/json"}, + "body": {"success": False, "error": str(e)} + } + + async def stop(self): + """Stop the P2P server.""" + if self.peer: + try: + await self.peer.destroy() + logger.info("P2P peer stopped") + except Exception as e: + logger.error(f"Error stopping P2P peer: {e}") + finally: + self.peer = None diff --git a/libs/python/agent/agent/proxy/server.py b/libs/python/agent/agent/proxy/server.py new file mode 100644 index 00000000..84dce61d --- /dev/null +++ b/libs/python/agent/agent/proxy/server.py @@ -0,0 +1,175 @@ +""" +Proxy server implementation supporting both HTTP and P2P connections. +""" + +import asyncio +import json +import logging +from typing import Dict, Any, Optional +from starlette.applications import Starlette +from starlette.routing import Route +from starlette.requests import Request +from starlette.responses import JSONResponse +from starlette.middleware import Middleware +from starlette.middleware.cors import CORSMiddleware +import uvicorn + +from .handlers import ResponsesHandler + +logger = logging.getLogger(__name__) + + +class ProxyServer: + """Proxy server that can serve over HTTP and P2P.""" + + def __init__(self, host: str = "0.0.0.0", port: int = 8000): + self.host = host + self.port = port + self.handler = ResponsesHandler() + self.app = self._create_app() + + def _create_app(self) -> Starlette: + """Create Starlette application with routes.""" + + async def responses_endpoint(request: Request) -> JSONResponse: + """Handle POST /responses requests.""" + try: + # Parse JSON body + body = await request.body() + request_data = json.loads(body.decode('utf-8')) + + # Process the request + result = await self.handler.process_request(request_data) + + return JSONResponse(result) + + except json.JSONDecodeError: + return JSONResponse({ + "success": False, + "error": "Invalid JSON in request body" + }, status_code=400) + except Exception as e: + logger.error(f"Error in responses endpoint: {e}") + return JSONResponse({ + "success": False, + "error": str(e) + }, status_code=500) + + async def health_endpoint(request: Request) -> JSONResponse: + """Health check endpoint.""" + return JSONResponse({"status": "healthy"}) + + routes = [ + Route("/responses", responses_endpoint, methods=["POST"]), + Route("/health", health_endpoint, methods=["GET"]), + ] + + middleware = [ + Middleware(CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"]) + ] + + return Starlette(routes=routes, middleware=middleware) + + async def start_http(self): + """Start HTTP server.""" + logger.info(f"Starting HTTP server on {self.host}:{self.port}") + config = uvicorn.Config( + self.app, + host=self.host, + port=self.port, + log_level="info" + ) + server = uvicorn.Server(config) + await server.serve() + + async def start_p2p(self, peer_id: Optional[str] = None, signaling_server: Optional[Dict[str, Any]] = None): + """Start P2P server using peerjs-python.""" + try: + from .p2p_server import P2PServer + + p2p_server = P2PServer( + handler=self.handler, + peer_id=peer_id, + signaling_server=signaling_server + ) + await p2p_server.start() + + except ImportError: + logger.error("P2P dependencies not available. Install peerjs-python for P2P support.") + raise + + async def start(self, mode: str = "http", **kwargs): + """ + Start the server in specified mode. + + Args: + mode: "http", "p2p", or "both" + **kwargs: Additional arguments for specific modes + """ + if mode == "http": + await self.start_http() + elif mode == "p2p": + await self.start_p2p(**kwargs) + elif mode == "both": + # Start both HTTP and P2P servers concurrently + tasks = [ + asyncio.create_task(self.start_http()), + asyncio.create_task(self.start_p2p(**kwargs)) + ] + await asyncio.gather(*tasks) + else: + raise ValueError(f"Invalid mode: {mode}. Must be 'http', 'p2p', or 'both'") + + async def cleanup(self): + """Clean up resources.""" + await self.handler.cleanup() + + +async def main(): + """Main entry point for running the proxy server.""" + import argparse + + parser = argparse.ArgumentParser(description="ComputerAgent Proxy Server") + parser.add_argument("--host", default="0.0.0.0", help="Host to bind to") + parser.add_argument("--port", type=int, default=8000, help="Port to bind to") + parser.add_argument("--mode", choices=["http", "p2p", "both"], default="http", + help="Server mode") + parser.add_argument("--peer-id", help="Peer ID for P2P mode") + parser.add_argument("--signaling-host", default="0.peerjs.com", help="Signaling server host") + parser.add_argument("--signaling-port", type=int, default=443, help="Signaling server port") + parser.add_argument("--signaling-secure", action="store_true", help="Use secure signaling") + + args = parser.parse_args() + + # Set up logging + logging.basicConfig(level=logging.INFO) + + # Create server + server = ProxyServer(host=args.host, port=args.port) + + # Prepare P2P kwargs if needed + p2p_kwargs = {} + if args.mode in ["p2p", "both"]: + p2p_kwargs = { + "peer_id": args.peer_id, + "signaling_server": { + "host": args.signaling_host, + "port": args.signaling_port, + "secure": args.signaling_secure + } + } + + try: + await server.start(mode=args.mode, **p2p_kwargs) + except KeyboardInterrupt: + logger.info("Shutting down server...") + finally: + await server.cleanup() + + +if __name__ == "__main__": + asyncio.run(main()) From 61cd5e22cc88fbe6a77e2bdab58d3f0da7f0e359 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 14 Aug 2025 13:27:36 -0400 Subject: [PATCH 02/31] added proxy docs --- .../docs/libraries/agent/PeerJS-API.mdx | 306 ++++++++++++++++++ .../content/docs/libraries/agent/REST-API.mdx | 166 ++++++++++ docs/content/docs/libraries/agent/index.mdx | 34 ++ 3 files changed, 506 insertions(+) create mode 100644 docs/content/docs/libraries/agent/PeerJS-API.mdx create mode 100644 docs/content/docs/libraries/agent/REST-API.mdx diff --git a/docs/content/docs/libraries/agent/PeerJS-API.mdx b/docs/content/docs/libraries/agent/PeerJS-API.mdx new file mode 100644 index 00000000..f3edea63 --- /dev/null +++ b/docs/content/docs/libraries/agent/PeerJS-API.mdx @@ -0,0 +1,306 @@ +--- +title: PeerJS Proxy API Reference +description: Reference for the P2P WebRTC endpoint of the Agent Proxy Server. +--- + +The Agent Proxy Server supports peer-to-peer (P2P) connections using WebRTC through the PeerJS protocol. This allows direct browser-to-agent communication without intermediary servers. + +## Connection Setup + +### Prerequisites +- Install peerjs-python: `pip install peerjs-python aiortc` +- Running signaling server (default: localhost:9000) + +### Starting P2P Server +```bash +# P2P only +python -m agent.proxy.cli --mode p2p --peer-id computer-agent-proxy + +# Both HTTP and P2P +python -m agent.proxy.cli --mode both --peer-id computer-agent-proxy + +# Custom signaling server +python -m agent.proxy.cli --mode p2p \ + --peer-id my-agent \ + --signaling-host signaling.example.com \ + --signaling-port 443 \ + --signaling-secure +``` + +## Client Connection (Python) + +### Basic Connection +```python +import asyncio +import json +from peerjs import Peer, PeerOptions, ConnectionEventType +from aiortc import RTCConfiguration, RTCIceServer + +async def connect_to_agent(): + # Set up peer options + options = PeerOptions( + host="localhost", + port=9000, + secure=False, + config=RTCConfiguration( + iceServers=[RTCIceServer(urls="stun:stun.l.google.com:19302")] + ) + ) + + # Create client peer + client_peer = Peer(id="my-client", peer_options=options) + await client_peer.start() + + # Connect to agent proxy + connection = client_peer.connect("computer-agent-proxy") + + @connection.on(ConnectionEventType.Open) + async def connection_open(): + print("Connected to agent proxy") + + # Send agent request + request = { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Hello from P2P client!" + } + await connection.send(json.dumps(request)) + + @connection.on(ConnectionEventType.Data) + async def connection_data(data): + response = json.loads(data) if isinstance(data, str) else data + print(f"Agent response: {response}") + await client_peer.destroy() + + # Keep connection alive + await asyncio.sleep(10) + +asyncio.run(connect_to_agent()) +``` + +### HTTP-like Requests over P2P +```python +import asyncio +import json +from peerjs import Peer, PeerOptions, ConnectionEventType + +async def http_over_p2p(): + # Connect to peer + client_peer = Peer(id="http-client", peer_options=options) + await client_peer.start() + connection = client_peer.connect("computer-agent-proxy") + + @connection.on(ConnectionEventType.Open) + async def connection_open(): + # Send HTTP-like request + http_request = { + "method": "POST", + "path": "/responses", + "body": { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Take a screenshot and describe what you see" + } + } + await connection.send(json.dumps(http_request)) + + @connection.on(ConnectionEventType.Data) + async def connection_data(data): + response = json.loads(data) + # Response format: {"status": 200, "headers": {...}, "body": {...}} + print(f"Status: {response['status']}") + print(f"Body: {response['body']}") + +asyncio.run(http_over_p2p()) +``` + +## Client Connection (JavaScript/Browser) + +### Basic Browser Client +```javascript +import Peer from 'peerjs'; + +// Create peer connection +const peer = new Peer('browser-client', { + host: 'localhost', + port: 9000, + path: '/peerjs' +}); + +// Connect to agent proxy +const conn = peer.connect('computer-agent-proxy'); + +conn.on('open', () => { + console.log('Connected to agent proxy'); + + // Send agent request + const request = { + model: 'anthropic/claude-3-5-sonnet-20241022', + input: 'Hello from browser!' + }; + conn.send(JSON.stringify(request)); +}); + +conn.on('data', (data) => { + const response = JSON.parse(data); + console.log('Agent response:', response); +}); + +conn.on('error', (err) => { + console.error('Connection error:', err); +}); +``` + +### Multi-modal Browser Request +```javascript +conn.on('open', () => { + const request = { + model: 'anthropic/claude-3-5-sonnet-20241022', + input: [ + { + role: 'user', + content: [ + { type: 'input_text', text: 'Analyze this image' }, + { + type: 'input_image', + image_url: 'https://example.com/image.jpg' + } + ] + } + ] + }; + conn.send(JSON.stringify(request)); +}); +``` + +## Message Formats + +### Direct Agent Request +Send agent requests directly as JSON: +```json +{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Your instruction here", + "agent_kwargs": { + "save_trajectory": true + } +} +``` + +### HTTP-like Request +Send HTTP-style requests for REST API compatibility: +```json +{ + "method": "POST", + "path": "/responses", + "body": { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Your instruction here" + } +} +``` + +## Response Formats + +### Direct Response +```json +{ + "success": true, + "result": { + // Agent response data + }, + "model": "anthropic/claude-3-5-sonnet-20241022" +} +``` + +### HTTP-like Response +```json +{ + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": { + "success": true, + "result": { + // Agent response data + } + } +} +``` + +### Welcome Message +Upon connection, the server sends a welcome message: +```json +{ + "type": "welcome", + "message": "Connected to ComputerAgent Proxy", + "endpoints": ["/responses"] +} +``` + +## Connection Events + +### Client Events +- `open`: Connection established +- `data`: Data received from agent +- `close`: Connection closed +- `error`: Connection error + +### Server Events +- `connection`: New peer connection +- `error`: Server error + +## Configuration + +### ICE Servers +Default STUN servers for NAT traversal: +```javascript +[ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:stun1.l.google.com:19302" } +] +``` + +### Custom Configuration +```python +options = PeerOptions( + host="your-signaling-server.com", + port=443, + secure=True, + config=RTCConfiguration( + iceServers=[ + RTCIceServer(urls="stun:stun.l.google.com:19302"), + RTCIceServer( + urls="turn:your-turn-server.com:3478", + username="user", + credential="pass" + ) + ] + ) +) +``` + +## Error Handling + +### Connection Errors +```python +@connection.on(ConnectionEventType.Error) +async def connection_error(error): + print(f"Connection error: {error}") + # Implement reconnection logic +``` + +### Request Errors +Invalid requests return error responses: +```json +{ + "success": false, + "error": "Invalid JSON in request" +} +``` + +## Security Considerations + +- WebRTC connections are encrypted by default +- Signaling server should use HTTPS/WSS in production +- Consider implementing peer authentication +- Use TURN servers for better connectivity in restrictive networks diff --git a/docs/content/docs/libraries/agent/REST-API.mdx b/docs/content/docs/libraries/agent/REST-API.mdx new file mode 100644 index 00000000..9341c247 --- /dev/null +++ b/docs/content/docs/libraries/agent/REST-API.mdx @@ -0,0 +1,166 @@ +--- +title: REST Proxy API Reference +description: Reference for the /responses REST endpoint of the Agent Proxy Server. +--- + +The Agent Proxy Server exposes a REST endpoint for ComputerAgent execution: + +- `http://localhost:8000/responses` + +## Starting HTTP Server + +```bash +python -m agent.proxy.cli --mode http +``` + +## POST /responses + +- Accepts agent requests as JSON in the request body +- Returns the first result from ComputerAgent execution +- Supports both simple text and multi-modal inputs + +### Request Format +```json +{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Your instruction here", + "agent_kwargs": { + "save_trajectory": true, + "verbosity": 20 + }, + "computer_kwargs": { + "os_type": "linux", + "provider_type": "cloud" + } +} +``` + +### Multi-modal Request Format +```json +{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": [ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "what is in this image?"}, + { + "type": "input_image", + "image_url": "https://example.com/image.jpg" + } + ] + } + ] +} +``` + +### Required Parameters +- `model`: Model string (e.g., "anthropic/claude-3-5-sonnet-20241022") +- `input`: String or message array with the user's instruction + +### Optional Parameters +- `agent_kwargs`: Dictionary of ComputerAgent configuration options +- `computer_kwargs`: Dictionary of Computer instance configuration options + +### Example Request (Python) +```python +import requests + +url = "http://localhost:8000/responses" +body = { + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Take a screenshot and tell me what you see" +} +resp = requests.post(url, json=body) +print(resp.json()) +``` + +### Example Request (cURL) +```bash +curl http://localhost:8000/responses \ + -H "Content-Type: application/json" \ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Tell me a three sentence bedtime story about a unicorn." + }' +``` + +### Multi-modal Example (cURL) +```bash +curl http://localhost:8000/responses \ + -H "Content-Type: application/json" \ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": [ + { + "role": "user", + "content": [ + {"type": "input_text", "text": "what is in this image?"}, + { + "type": "input_image", + "image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" + } + ] + } + ] + }' +``` + +### Response Format +```json +{ + "success": true, + "result": { + // Agent response data + }, + "model": "anthropic/claude-3-5-sonnet-20241022" +} +``` + +### Error Response Format +```json +{ + "success": false, + "error": "Error description", + "model": "anthropic/claude-3-5-sonnet-20241022" +} +``` + +## GET /health + +Health check endpoint that returns server status. + +### Response Format +```json +{ + "status": "healthy" +} +``` + +## Configuration + +### Environment Variables +- `CUA_CONTAINER_NAME`: Default container name for cloud provider +- `CUA_API_KEY`: Default API key for cloud provider + +### Agent Configuration (`agent_kwargs`) +Common options: +- `save_trajectory`: Boolean - Save conversation trajectory +- `verbosity`: Integer - Logging level (10=DEBUG, 20=INFO, etc.) +- `max_trajectory_budget`: Float - Budget limit for trajectory +- `telemetry_enabled`: Boolean or Dict - Enable telemetry tracking + +### Computer Configuration (`computer_kwargs`) +Common options: +- `os_type`: String - "linux", "windows", "macos" +- `provider_type`: String - "cloud", "local", "docker" +- `name`: String - Instance name +- `api_key`: String - Provider API key + +## Error Handling + +Common error scenarios: +- **400 Bad Request**: Invalid JSON or missing required parameters +- **500 Internal Server Error**: Agent execution errors or computer setup failures + +All errors return a structured JSON response with `success: false` and an `error` field describing the issue. diff --git a/docs/content/docs/libraries/agent/index.mdx b/docs/content/docs/libraries/agent/index.mdx index f0e1ab77..b45485a7 100644 --- a/docs/content/docs/libraries/agent/index.mdx +++ b/docs/content/docs/libraries/agent/index.mdx @@ -114,3 +114,37 @@ agent = ComputerAgent( callbacks=[ImageRetentionCallback(only_n_most_recent_images=3)] ) ``` + +## Proxy Server API + +The Agent library includes a proxy server that exposes ComputerAgent functionality over HTTP and P2P (WebRTC) connections, allowing remote clients to interact with agents through REST-like APIs. + +### Starting the Proxy Server + +```bash +# HTTP server (default) +python -m agent.proxy.cli + +# P2P server with WebRTC +python -m agent.proxy.cli --mode p2p --peer-id computer-agent-proxy + +# Both HTTP and P2P +python -m agent.proxy.cli --mode both --peer-id computer-agent-proxy +``` + +### API References + +- [REST API Reference](./REST-API) - HTTP endpoints for agent execution +- [PeerJS API Reference](./PeerJS-API) - P2P WebRTC connections for direct browser-to-agent communication + +### Quick Example + +```bash +curl http://localhost:8000/responses \ + -H "Content-Type: application/json" \ + -d '{ + "model": "anthropic/claude-3-5-sonnet-20241022", + "input": "Take a screenshot and tell me what you see" + }' +``` +``` From 43d566831bc205d9a4e8d009ea0aa317e4065066 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 19 Aug 2025 17:35:02 -0400 Subject: [PATCH 03/31] Changed proxy server startup message --- libs/python/agent/agent/proxy/p2p_server.py | 37 +++++++++++---------- libs/python/agent/agent/proxy/server.py | 3 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/libs/python/agent/agent/proxy/p2p_server.py b/libs/python/agent/agent/proxy/p2p_server.py index ed50f4ba..e658428b 100644 --- a/libs/python/agent/agent/proxy/p2p_server.py +++ b/libs/python/agent/agent/proxy/p2p_server.py @@ -5,6 +5,7 @@ P2P server implementation using peerjs-python for WebRTC connections. import asyncio import json import logging +import uuid from typing import Dict, Any, Optional logger = logging.getLogger(__name__) @@ -15,7 +16,7 @@ class P2PServer: def __init__(self, handler, peer_id: Optional[str] = None, signaling_server: Optional[Dict[str, Any]] = None): self.handler = handler - self.peer_id = peer_id or "computer-agent-proxy" + self.peer_id = peer_id or uuid.uuid4().hex self.signaling_server = signaling_server or { "host": "0.peerjs.com", "port": 443, @@ -26,8 +27,8 @@ class P2PServer: async def start(self): """Start P2P server with WebRTC connections.""" try: - from peerjs import Peer, PeerOptions, PeerEventType, ConnectionEventType - from aiortc import RTCConfiguration, RTCIceServer + from peerjs_py.peer import Peer, PeerOptions + from peerjs_py.enums import PeerEventType, ConnectionEventType # Set up peer options ice_servers = [ @@ -35,27 +36,29 @@ class P2PServer: {"urls": "stun:stun1.l.google.com:19302"} ] - options = PeerOptions( + # Create peer with PeerOptions (config should be a dict, not RTCConfiguration) + peer_options = PeerOptions( host=self.signaling_server["host"], port=self.signaling_server["port"], secure=self.signaling_server["secure"], - config=RTCConfiguration( - iceServers=[RTCIceServer(**srv) for srv in ice_servers] - ) + config={ + "iceServers": ice_servers + } ) # Create peer - self.peer = Peer(id=self.peer_id, peer_options=options) + self.peer = Peer(id=self.peer_id, options=peer_options) await self.peer.start() - logger.info(f"P2P peer started with ID: {self.peer_id}") + # logger.info(f"P2P peer started with ID: {self.peer_id}") + print(f"Agent proxy started at peer://{self.peer_id}") - # Set up connection handlers - @self.peer.on(PeerEventType.Connection) + # Set up connection handlers using string event names + @self.peer.on('connection') async def peer_connection(peer_connection): logger.info(f"Remote peer {peer_connection.peer} trying to establish connection") await self._setup_connection_handlers(peer_connection) - @self.peer.on(PeerEventType.Error) + @self.peer.on('error') async def peer_error(error): logger.error(f"Peer error: {error}") @@ -74,9 +77,9 @@ class P2PServer: async def _setup_connection_handlers(self, peer_connection): """Set up handlers for a peer connection.""" try: - from peerjs import ConnectionEventType + # Use string event names instead of enum types - @peer_connection.on(ConnectionEventType.Open) + @peer_connection.on('open') async def connection_open(): logger.info(f"Connection opened with peer {peer_connection.peer}") @@ -88,7 +91,7 @@ class P2PServer: } await peer_connection.send(json.dumps(welcome_msg)) - @peer_connection.on(ConnectionEventType.Data) + @peer_connection.on('data') async def connection_data(data): logger.debug(f"Data received from peer {peer_connection.peer}: {data}") @@ -122,11 +125,11 @@ class P2PServer: } await peer_connection.send(json.dumps(error_response)) - @peer_connection.on(ConnectionEventType.Close) + @peer_connection.on('close') async def connection_close(): logger.info(f"Connection closed with peer {peer_connection.peer}") - @peer_connection.on(ConnectionEventType.Error) + @peer_connection.on('error') async def connection_error(error): logger.error(f"Connection error with peer {peer_connection.peer}: {error}") diff --git a/libs/python/agent/agent/proxy/server.py b/libs/python/agent/agent/proxy/server.py index 84dce61d..2530a096 100644 --- a/libs/python/agent/agent/proxy/server.py +++ b/libs/python/agent/agent/proxy/server.py @@ -76,7 +76,8 @@ class ProxyServer: async def start_http(self): """Start HTTP server.""" - logger.info(f"Starting HTTP server on {self.host}:{self.port}") + # logger.info(f"Starting HTTP server on {self.host}:{self.port}") + print(f"Agent proxy started at http://{self.host}:{self.port}") config = uvicorn.Config( self.app, host=self.host, From 7953de3ba4a5aded99245658d6b6e0e070f683b3 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 19 Aug 2025 17:35:24 -0400 Subject: [PATCH 04/31] Added typescript agent client SDK --- libs/typescript/agent/README.md | 193 ++++++++++++++++++ libs/typescript/agent/examples/README.md | 70 +++++++ .../agent/examples/playground-example.html | 146 +++++++++++++ libs/typescript/agent/package.json | 54 +++++ libs/typescript/agent/src/client.ts | 181 ++++++++++++++++ libs/typescript/agent/src/index.ts | 15 ++ libs/typescript/agent/src/types.ts | 43 ++++ libs/typescript/agent/tests/client.test.ts | 34 +++ libs/typescript/agent/tsconfig.json | 28 +++ libs/typescript/agent/tsdown.config.ts | 8 + libs/typescript/agent/vitest.config.ts | 7 + 11 files changed, 779 insertions(+) create mode 100644 libs/typescript/agent/README.md create mode 100644 libs/typescript/agent/examples/README.md create mode 100644 libs/typescript/agent/examples/playground-example.html create mode 100644 libs/typescript/agent/package.json create mode 100644 libs/typescript/agent/src/client.ts create mode 100644 libs/typescript/agent/src/index.ts create mode 100644 libs/typescript/agent/src/types.ts create mode 100644 libs/typescript/agent/tests/client.test.ts create mode 100644 libs/typescript/agent/tsconfig.json create mode 100644 libs/typescript/agent/tsdown.config.ts create mode 100644 libs/typescript/agent/vitest.config.ts diff --git a/libs/typescript/agent/README.md b/libs/typescript/agent/README.md new file mode 100644 index 00000000..36dc89c9 --- /dev/null +++ b/libs/typescript/agent/README.md @@ -0,0 +1,193 @@ +# @trycua/agent + +TypeScript SDK for CUA agent interaction. Connect to CUA agent proxies via HTTP/HTTPS or peer-to-peer (WebRTC) connections. + +## Installation + +```bash +npm install @trycua/agent +# or +pnpm add @trycua/agent +# or +yarn add @trycua/agent +``` + +## Usage + +### Basic Usage + +```typescript +import AgentClient from "@trycua/agent"; + +// Connect to HTTP server +const client = new AgentClient("https://localhost:8000"); + +// Connect to peer +const peerClient = new AgentClient("peer://my-agent-proxy"); + +// Send a simple text request +const response = await client.responses.create({ + model: "anthropic/claude-3-5-sonnet-20241022", + input: "Write a one-sentence bedtime story about a unicorn." +}); + +console.log(response.result); +``` + +### Multi-modal Requests + +```typescript +const response = await client.responses.create({ + model: "anthropic/claude-3-5-sonnet-20241022", + input: [ + { + role: "user", + content: [ + { type: "input_text", text: "What is in this image?" }, + { + type: "input_image", + image_url: "https://example.com/image.jpg" + } + ] + } + ] +}); +``` + +### Advanced Configuration + +```typescript +const client = new AgentClient("https://localhost:8000", { + timeout: 60000, // 60 second timeout + retries: 5 // 5 retry attempts +}); + +const response = await client.responses.create({ + model: "anthropic/claude-3-5-sonnet-20241022", + input: "Hello, world!", + agent_kwargs: { + save_trajectory: true, + verbosity: 20 + }, + computer_kwargs: { + os_type: "linux", + provider_type: "cloud" + } +}); +``` + +### Health Check + +```typescript +const health = await client.health(); +console.log(health.status); // 'healthy', 'unhealthy', 'unreachable', 'connected', 'disconnected' +``` + +### Cleanup + +```typescript +// Clean up peer connections when done +await client.disconnect(); +``` + +## API Reference + +### AgentClient + +#### Constructor + +```typescript +new AgentClient(url: string, options?: AgentClientOptions) +``` + +- `url`: Connection URL. Supports `http://`, `https://`, or `peer://` protocols +- `options`: Optional configuration object + +#### Methods + +##### responses.create(request: AgentRequest): Promise + +Send a request to the agent and get a response. + +##### health(): Promise<{status: string}> + +Check the health/connection status of the agent. + +##### disconnect(): Promise + +Clean up resources and close connections. + +### Types + +#### AgentRequest + +```typescript +interface AgentRequest { + model: string; + input: string | AgentMessage[]; + agent_kwargs?: { + save_trajectory?: boolean; + verbosity?: number; + [key: string]: any; + }; + computer_kwargs?: { + os_type?: string; + provider_type?: string; + [key: string]: any; + }; +} +``` + +#### AgentResponse + +```typescript +interface AgentResponse { + success: boolean; + result?: any; + model: string; + error?: string; +} +``` + +## Connection Types + +### HTTP/HTTPS + +Connect to a CUA agent proxy server: + +```typescript +const client = new AgentClient("https://my-agent-server.com:8000"); +``` + +### Peer-to-Peer (WebRTC) + +Connect directly to another peer using WebRTC: + +```typescript +const client = new AgentClient("peer://agent-proxy-peer-id"); +``` + +The client uses PeerJS with default configuration for peer connections. + +## Error Handling + +```typescript +try { + const response = await client.responses.create({ + model: "anthropic/claude-3-5-sonnet-20241022", + input: "Hello!" + }); + + if (response.success) { + console.log(response.result); + } else { + console.error("Agent error:", response.error); + } +} catch (error) { + console.error("Connection error:", error.message); +} +``` + +## License + +MIT diff --git a/libs/typescript/agent/examples/README.md b/libs/typescript/agent/examples/README.md new file mode 100644 index 00000000..8e3148fb --- /dev/null +++ b/libs/typescript/agent/examples/README.md @@ -0,0 +1,70 @@ +# CUA Agent Client Examples + +This directory contains examples demonstrating how to use the `@trycua/agent` client library. + +## Browser Example + +### `browser-example.html` + +A simple HTML page that demonstrates using the CUA Agent Client in a browser environment. + +**Features:** +- Connect to HTTP/HTTPS or P2P (peer://) agent proxies +- Send text messages to any supported model +- View responses in real-time +- Health check functionality +- Clear, simple interface with no external dependencies + +**Usage:** + +1. **Build the library first:** + ```bash + cd ../ + pnpm build + ``` + +2. **Start a local web server** (required for ES modules): + ```bash + # Option 1: Using Python + python -m http.server 8080 + + # Option 2: Using Node.js (if you have http-server installed) + npx http-server -p 8080 + + # Option 3: Using any other local server + ``` + +3. **Open in browser:** + Navigate to `http://localhost:8080/examples/browser-example.html` + +4. **Configure and test:** + - Enter an agent URL (e.g., `https://localhost:8000` or `peer://some-peer-id`) + - Enter a model name (e.g., `anthropic/claude-3-5-sonnet-20241022`) + - Type a message and click "Send Message" or press Enter + - View the response in the output textarea + +**Supported URLs:** +- **HTTP/HTTPS**: `https://localhost:8000`, `http://my-agent-server.com:8080` +- **Peer-to-Peer**: `peer://computer-agent-proxy`, `peer://any-peer-id` + +**Example Models:** +- `anthropic/claude-3-5-sonnet-20241022` +- `openai/gpt-4` +- `huggingface-local/microsoft/UI-TARS-7B` + +**Note:** Make sure you have a CUA agent proxy server running at the specified URL before testing. + +## Running Agent Proxy Server + +To test the examples, you'll need a CUA agent proxy server running: + +```bash +# HTTP server (default port 8000) +python -m agent.proxy.cli + +# P2P server +python -m agent.proxy.cli --mode p2p + +# Both HTTP and P2P +python -m agent.proxy.cli --mode both +``` diff --git a/libs/typescript/agent/examples/playground-example.html b/libs/typescript/agent/examples/playground-example.html new file mode 100644 index 00000000..e1d51d71 --- /dev/null +++ b/libs/typescript/agent/examples/playground-example.html @@ -0,0 +1,146 @@ + + + + + + CUA Agent Client Browser Example + + +

CUA Agent Client Browser Example

+ +
+

Configuration

+
+

+ +
+

+
+ +
+

Chat

+
+

+ + + +

+ +
+ +
+ + + + + diff --git a/libs/typescript/agent/package.json b/libs/typescript/agent/package.json new file mode 100644 index 00000000..a1585db2 --- /dev/null +++ b/libs/typescript/agent/package.json @@ -0,0 +1,54 @@ +{ + "name": "@trycua/agent", + "version": "0.1.0", + "packageManager": "pnpm@10.11.0", + "description": "TypeScript SDK for CUA agent interaction", + "type": "module", + "license": "MIT", + "homepage": "https://github.com/trycua/cua/tree/main/libs/typescript/agent", + "bugs": { + "url": "https://github.com/trycua/cua/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/trycua/cua.git" + }, + "author": "cua", + "files": [ + "dist" + ], + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": "./dist/index.js", + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "lint": "biome lint .", + "lint:fix": "biome lint --fix .", + "build": "tsdown", + "dev": "tsdown --watch", + "test": "vitest", + "typecheck": "tsc --noEmit", + "release": "bumpp && pnpm publish", + "prepublishOnly": "pnpm run build" + }, + "dependencies": { + "@trycua/core": "^0.1.2", + "pino": "^9.7.0", + "peerjs": "^1.5.4" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/node": "^22.15.17", + "bumpp": "^10.1.0", + "happy-dom": "^17.4.7", + "tsdown": "^0.3.0", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + } +} diff --git a/libs/typescript/agent/src/client.ts b/libs/typescript/agent/src/client.ts new file mode 100644 index 00000000..6a2ec0a2 --- /dev/null +++ b/libs/typescript/agent/src/client.ts @@ -0,0 +1,181 @@ +import pkg from 'peerjs'; +const { Peer, DataConnection } = pkg; +import type { AgentRequest, AgentResponse, ConnectionType, AgentClientOptions } from './types.js'; + +export class AgentClient { + private url: string; + private connectionType: ConnectionType; + private options: AgentClientOptions; + private peer?: Peer; + private connection?: DataConnection; + + constructor(url: string, options: AgentClientOptions = {}) { + this.url = url; + this.options = { + timeout: 30000, + retries: 3, + ...options, + }; + + // Determine connection type from URL + if (url.startsWith('http://') || url.startsWith('https://')) { + this.connectionType = url.startsWith('https://') ? 'https' : 'http'; + } else if (url.startsWith('peer://')) { + this.connectionType = 'peer'; + } else { + throw new Error('Invalid URL format. Must start with http://, https://, or peer://'); + } + } + + // Main responses API matching the desired usage pattern + public responses = { + create: async (request: AgentRequest): Promise => { + return this.sendRequest(request); + } + }; + + private async sendRequest(request: AgentRequest): Promise { + switch (this.connectionType) { + case 'http': + case 'https': + return this.sendHttpRequest(request); + case 'peer': + return this.sendPeerRequest(request); + default: + throw new Error(`Unsupported connection type: ${this.connectionType}`); + } + } + + private async sendHttpRequest(request: AgentRequest): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), this.options.timeout); + + try { + const response = await fetch(`${this.url}/responses`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request), + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data as AgentResponse; + } catch (error) { + clearTimeout(timeoutId); + if (error instanceof Error) { + throw new Error(`Failed to send HTTP request: ${error.message}`); + } + throw error; + } + } + + private async sendPeerRequest(request: AgentRequest): Promise { + // Extract peer ID from peer:// URL + const peerId = this.url.replace('peer://', ''); + + if (!this.peer) { + // Initialize peer connection with default options as requested + this.peer = new Peer(); + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error('Peer connection timeout')); + }, this.options.timeout); + + this.peer!.on('open', () => { + // Connect to the target peer + this.connection = this.peer!.connect(peerId); + + this.connection.on('open', () => { + // Send the request + this.connection!.send(JSON.stringify(request)); + }); + + this.connection.on('data', (data: any) => { + clearTimeout(timeout); + try { + const response = typeof data === 'string' ? JSON.parse(data) : data; + resolve(response as AgentResponse); + } catch (error) { + reject(new Error('Failed to parse peer response')); + } + }); + + this.connection.on('error', (error: any) => { + clearTimeout(timeout); + reject(new Error(`Peer connection error: ${error}`)); + }); + }); + + this.peer!.on('error', (error: any) => { + clearTimeout(timeout); + reject(new Error(`Peer error: ${error}`)); + }); + }); + } else { + // Reuse existing connection + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error('Peer request timeout')); + }, this.options.timeout); + + if (this.connection && this.connection.open) { + this.connection.send(JSON.stringify(request)); + + const handleData = (data: any) => { + clearTimeout(timeout); + this.connection!.off('data', handleData); + try { + const response = typeof data === 'string' ? JSON.parse(data) : data; + resolve(response as AgentResponse); + } catch (error) { + reject(new Error('Failed to parse peer response')); + } + }; + + this.connection.on('data', handleData); + } else { + clearTimeout(timeout); + reject(new Error('Peer connection not available')); + } + }); + } + } + + // Health check method + async health(): Promise<{ status: string }> { + if (this.connectionType === 'peer') { + return { status: this.peer?.open ? 'connected' : 'disconnected' }; + } + + try { + const response = await fetch(`${this.url}/health`); + if (response.ok) { + return { status: 'healthy' }; + } + return { status: 'unhealthy' }; + } catch { + return { status: 'unreachable' }; + } + } + + // Clean up resources + async disconnect(): Promise { + if (this.connection) { + this.connection.close(); + this.connection = undefined; + } + if (this.peer) { + this.peer.destroy(); + this.peer = undefined; + } + } +} diff --git a/libs/typescript/agent/src/index.ts b/libs/typescript/agent/src/index.ts new file mode 100644 index 00000000..225fa0b3 --- /dev/null +++ b/libs/typescript/agent/src/index.ts @@ -0,0 +1,15 @@ +// Export the main AgentClient class as default +export { AgentClient as default } from './client.js'; + +// Also export as named export for flexibility +export { AgentClient } from './client.js'; + +// Export types for TypeScript users +export type { + AgentRequest, + AgentResponse, + AgentMessage, + AgentContent, + ConnectionType, + AgentClientOptions, +} from './types.js'; diff --git a/libs/typescript/agent/src/types.ts b/libs/typescript/agent/src/types.ts new file mode 100644 index 00000000..ea32915f --- /dev/null +++ b/libs/typescript/agent/src/types.ts @@ -0,0 +1,43 @@ +// Request types matching the Python proxy API +export interface AgentRequest { + model: string; + input: string | AgentMessage[]; + agent_kwargs?: { + save_trajectory?: boolean; + verbosity?: number; + [key: string]: any; + }; + computer_kwargs?: { + os_type?: string; + provider_type?: string; + [key: string]: any; + }; +} + +// Multi-modal message types +export interface AgentMessage { + role: 'user' | 'assistant'; + content: AgentContent[]; +} + +export interface AgentContent { + type: 'input_text' | 'input_image'; + text?: string; + image_url?: string; +} + +// Response types +export interface AgentResponse { + success: boolean; + result?: any; + model: string; + error?: string; +} + +// Connection types +export type ConnectionType = 'http' | 'https' | 'peer'; + +export interface AgentClientOptions { + timeout?: number; + retries?: number; +} diff --git a/libs/typescript/agent/tests/client.test.ts b/libs/typescript/agent/tests/client.test.ts new file mode 100644 index 00000000..b267b122 --- /dev/null +++ b/libs/typescript/agent/tests/client.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import AgentClient from '../src/index.js'; + +describe('AgentClient', () => { + it('should create client with HTTP URL', () => { + const client = new AgentClient('https://localhost:8000'); + expect(client).toBeDefined(); + expect(client.responses).toBeDefined(); + expect(typeof client.responses.create).toBe('function'); + }); + + it('should create client with peer URL', () => { + const client = new AgentClient('peer://test-peer-id'); + expect(client).toBeDefined(); + expect(client.responses).toBeDefined(); + expect(typeof client.responses.create).toBe('function'); + }); + + it('should throw error for invalid URL', () => { + expect(() => { + new AgentClient('invalid://url'); + }).toThrow('Invalid URL format'); + }); + + it('should have health method', async () => { + const client = new AgentClient('https://localhost:8000'); + expect(typeof client.health).toBe('function'); + }); + + it('should have disconnect method', async () => { + const client = new AgentClient('https://localhost:8000'); + expect(typeof client.disconnect).toBe('function'); + }); +}); diff --git a/libs/typescript/agent/tsconfig.json b/libs/typescript/agent/tsconfig.json new file mode 100644 index 00000000..8d56b691 --- /dev/null +++ b/libs/typescript/agent/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": [ + "es2023" + ], + "moduleDetection": "force", + "module": "preserve", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "types": [ + "node" + ], + "allowSyntheticDefaultImports": true, + "strict": true, + "noUnusedLocals": true, + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/libs/typescript/agent/tsdown.config.ts b/libs/typescript/agent/tsdown.config.ts new file mode 100644 index 00000000..408990c1 --- /dev/null +++ b/libs/typescript/agent/tsdown.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + clean: true, +}) diff --git a/libs/typescript/agent/vitest.config.ts b/libs/typescript/agent/vitest.config.ts new file mode 100644 index 00000000..56feb5b7 --- /dev/null +++ b/libs/typescript/agent/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'happy-dom', + }, +}) From 829fde53ba8ee5b0d5b453294c4caec548fddabb Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 19 Aug 2025 17:38:25 -0400 Subject: [PATCH 05/31] Updated example --- libs/typescript/agent/README.md | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/libs/typescript/agent/README.md b/libs/typescript/agent/README.md index 36dc89c9..d07f869c 100644 --- a/libs/typescript/agent/README.md +++ b/libs/typescript/agent/README.md @@ -31,7 +31,7 @@ const response = await client.responses.create({ input: "Write a one-sentence bedtime story about a unicorn." }); -console.log(response.result); +console.log(response.output); ``` ### Multi-modal Requests @@ -169,25 +169,6 @@ const client = new AgentClient("peer://agent-proxy-peer-id"); The client uses PeerJS with default configuration for peer connections. -## Error Handling - -```typescript -try { - const response = await client.responses.create({ - model: "anthropic/claude-3-5-sonnet-20241022", - input: "Hello!" - }); - - if (response.success) { - console.log(response.result); - } else { - console.error("Agent error:", response.error); - } -} catch (error) { - console.error("Connection error:", error.message); -} -``` - ## License MIT From b202f768adf688165e8915015a3f3ca164582e12 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 19 Aug 2025 17:42:34 -0400 Subject: [PATCH 06/31] Fixed TS types --- libs/typescript/agent/src/index.ts | 16 ++++- libs/typescript/agent/src/types.ts | 107 ++++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 11 deletions(-) diff --git a/libs/typescript/agent/src/index.ts b/libs/typescript/agent/src/index.ts index 225fa0b3..32d2a28a 100644 --- a/libs/typescript/agent/src/index.ts +++ b/libs/typescript/agent/src/index.ts @@ -9,7 +9,21 @@ export type { AgentRequest, AgentResponse, AgentMessage, - AgentContent, + UserMessage, + AssistantMessage, + ReasoningMessage, + ComputerCallMessage, + ComputerCallOutputMessage, + OutputContent, + SummaryContent, + InputContent, + ComputerAction, + ClickAction, + TypeAction, + KeyAction, + ScrollAction, + WaitAction, + Usage, ConnectionType, AgentClientOptions, } from './types.js'; diff --git a/libs/typescript/agent/src/types.ts b/libs/typescript/agent/src/types.ts index ea32915f..e5c079f0 100644 --- a/libs/typescript/agent/src/types.ts +++ b/libs/typescript/agent/src/types.ts @@ -14,24 +14,111 @@ export interface AgentRequest { }; } -// Multi-modal message types -export interface AgentMessage { - role: 'user' | 'assistant'; - content: AgentContent[]; +// Agent message types - can be one of several different message types +export type AgentMessage = + | UserMessage + | AssistantMessage + | ReasoningMessage + | ComputerCallMessage + | ComputerCallOutputMessage; + +// User input message +export interface UserMessage { + role: 'user'; + content: string; } -export interface AgentContent { - type: 'input_text' | 'input_image'; +// Assistant response message +export interface AssistantMessage { + type: 'message'; + role: 'assistant'; + content: OutputContent[]; +} + +// Reasoning/thinking message +export interface ReasoningMessage { + type: 'reasoning'; + summary: SummaryContent[]; +} + +// Computer action call +export interface ComputerCallMessage { + type: 'computer_call'; + call_id: string; + status: 'completed' | 'failed' | 'pending'; + action: ComputerAction; +} + +// Computer action output (usually screenshot) +export interface ComputerCallOutputMessage { + type: 'computer_call_output'; + call_id: string; + output: InputContent; +} + +// Content types +export interface OutputContent { + type: 'output_text'; + text: string; +} + +export interface SummaryContent { + type: 'summary_text'; + text: string; +} + +export interface InputContent { + type: 'input_image' | 'input_text'; text?: string; image_url?: string; } +// Computer action types +export type ComputerAction = + | ClickAction + | TypeAction + | KeyAction + | ScrollAction + | WaitAction; + +export interface ClickAction { + type: 'click'; + coordinate: [number, number]; +} + +export interface TypeAction { + type: 'type'; + text: string; +} + +export interface KeyAction { + type: 'key'; + key: string; +} + +export interface ScrollAction { + type: 'scroll'; + coordinate: [number, number]; + direction: 'up' | 'down' | 'left' | 'right'; +} + +export interface WaitAction { + type: 'wait'; + seconds?: number; +} + +// Usage information +export interface Usage { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + response_cost: number; +} + // Response types export interface AgentResponse { - success: boolean; - result?: any; - model: string; - error?: string; + output: AgentMessage[]; + usage: Usage; } // Connection types From 56dfbd0dc48a44ddb81d835e50293a5b3b55f6ad Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 19 Aug 2025 17:44:54 -0400 Subject: [PATCH 07/31] Fixed TS types --- libs/typescript/agent/README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/libs/typescript/agent/README.md b/libs/typescript/agent/README.md index d07f869c..a59ddfda 100644 --- a/libs/typescript/agent/README.md +++ b/libs/typescript/agent/README.md @@ -142,13 +142,26 @@ interface AgentRequest { ```typescript interface AgentResponse { - success: boolean; - result?: any; - model: string; - error?: string; + output: AgentMessage[]; + usage: Usage; +} + +interface Usage { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + response_cost: number; } ``` +The `output` array contains the conversation history including: +- User messages +- Agent reasoning/thinking +- Computer actions and their results +- Final agent responses + +The `usage` object provides token counts and cost information for the request. + ## Connection Types ### HTTP/HTTPS From faec64029b27b838586eb4814b7bd6e39e5005ad Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 19 Aug 2025 17:46:41 -0400 Subject: [PATCH 08/31] Added minimal playground --- libs/typescript/agent/examples/playground-example.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/typescript/agent/examples/playground-example.html b/libs/typescript/agent/examples/playground-example.html index e1d51d71..8fd35a05 100644 --- a/libs/typescript/agent/examples/playground-example.html +++ b/libs/typescript/agent/examples/playground-example.html @@ -3,10 +3,10 @@ - CUA Agent Client Browser Example + CUA Agent Playground Example -

CUA Agent Client Browser Example

+

CUA Agent Playground Example

Configuration

@@ -23,7 +23,7 @@

- +


From 8277c722b283a1d0470192b0bc0c15a87f152439 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Thu, 21 Aug 2025 11:55:02 +0100 Subject: [PATCH 09/31] Setup browser bundling for Agent library --- libs/typescript/agent/package.json | 6 +- libs/typescript/agent/src/client.ts | 95 +-- libs/typescript/agent/tsdown.config.ts | 10 +- libs/typescript/package.json | 5 +- libs/typescript/pnpm-lock.yaml | 873 +++++++++++++++++++++++++ libs/typescript/pnpm-workspace.yaml | 1 + 6 files changed, 940 insertions(+), 50 deletions(-) diff --git a/libs/typescript/agent/package.json b/libs/typescript/agent/package.json index a1585db2..f77206df 100644 --- a/libs/typescript/agent/package.json +++ b/libs/typescript/agent/package.json @@ -39,15 +39,15 @@ }, "dependencies": { "@trycua/core": "^0.1.2", - "pino": "^9.7.0", - "peerjs": "^1.5.4" + "peerjs": "^1.5.4", + "pino": "^9.7.0" }, "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.15.17", "bumpp": "^10.1.0", "happy-dom": "^17.4.7", - "tsdown": "^0.3.0", + "tsdown": "^0.14.1", "typescript": "^5.7.2", "vitest": "^2.1.8" } diff --git a/libs/typescript/agent/src/client.ts b/libs/typescript/agent/src/client.ts index 6a2ec0a2..5ea073db 100644 --- a/libs/typescript/agent/src/client.ts +++ b/libs/typescript/agent/src/client.ts @@ -1,13 +1,17 @@ -import pkg from 'peerjs'; -const { Peer, DataConnection } = pkg; -import type { AgentRequest, AgentResponse, ConnectionType, AgentClientOptions } from './types.js'; +import {Peer} from "peerjs"; +import type { + AgentRequest, + AgentResponse, + ConnectionType, + AgentClientOptions, +} from "./types.js"; export class AgentClient { private url: string; private connectionType: ConnectionType; private options: AgentClientOptions; private peer?: Peer; - private connection?: DataConnection; + private connection?: any; constructor(url: string, options: AgentClientOptions = {}) { this.url = url; @@ -16,14 +20,16 @@ export class AgentClient { retries: 3, ...options, }; - + // Determine connection type from URL - if (url.startsWith('http://') || url.startsWith('https://')) { - this.connectionType = url.startsWith('https://') ? 'https' : 'http'; - } else if (url.startsWith('peer://')) { - this.connectionType = 'peer'; + if (url.startsWith("http://") || url.startsWith("https://")) { + this.connectionType = url.startsWith("https://") ? "https" : "http"; + } else if (url.startsWith("peer://")) { + this.connectionType = "peer"; } else { - throw new Error('Invalid URL format. Must start with http://, https://, or peer://'); + throw new Error( + "Invalid URL format. Must start with http://, https://, or peer://" + ); } } @@ -31,15 +37,15 @@ export class AgentClient { public responses = { create: async (request: AgentRequest): Promise => { return this.sendRequest(request); - } + }, }; private async sendRequest(request: AgentRequest): Promise { switch (this.connectionType) { - case 'http': - case 'https': + case "http": + case "https": return this.sendHttpRequest(request); - case 'peer': + case "peer": return this.sendPeerRequest(request); default: throw new Error(`Unsupported connection type: ${this.connectionType}`); @@ -48,13 +54,16 @@ export class AgentClient { private async sendHttpRequest(request: AgentRequest): Promise { const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), this.options.timeout); + const timeoutId = setTimeout( + () => controller.abort(), + this.options.timeout + ); try { const response = await fetch(`${this.url}/responses`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify(request), signal: controller.signal, @@ -79,43 +88,44 @@ export class AgentClient { private async sendPeerRequest(request: AgentRequest): Promise { // Extract peer ID from peer:// URL - const peerId = this.url.replace('peer://', ''); - + const peerId = this.url.replace("peer://", ""); + if (!this.peer) { // Initialize peer connection with default options as requested this.peer = new Peer(); - + return new Promise((resolve, reject) => { const timeout = setTimeout(() => { - reject(new Error('Peer connection timeout')); + reject(new Error("Peer connection timeout")); }, this.options.timeout); - this.peer!.on('open', () => { + this.peer!.on("open", () => { // Connect to the target peer this.connection = this.peer!.connect(peerId); - - this.connection.on('open', () => { + + this.connection.on("open", () => { // Send the request this.connection!.send(JSON.stringify(request)); }); - this.connection.on('data', (data: any) => { + this.connection.on("data", (data: any) => { clearTimeout(timeout); try { - const response = typeof data === 'string' ? JSON.parse(data) : data; + const response = + typeof data === "string" ? JSON.parse(data) : data; resolve(response as AgentResponse); } catch (error) { - reject(new Error('Failed to parse peer response')); + reject(new Error("Failed to parse peer response")); } }); - this.connection.on('error', (error: any) => { + this.connection.on("error", (error: any) => { clearTimeout(timeout); reject(new Error(`Peer connection error: ${error}`)); }); }); - this.peer!.on('error', (error: any) => { + this.peer!.on("error", (error: any) => { clearTimeout(timeout); reject(new Error(`Peer error: ${error}`)); }); @@ -124,27 +134,28 @@ export class AgentClient { // Reuse existing connection return new Promise((resolve, reject) => { const timeout = setTimeout(() => { - reject(new Error('Peer request timeout')); + reject(new Error("Peer request timeout")); }, this.options.timeout); if (this.connection && this.connection.open) { this.connection.send(JSON.stringify(request)); - + const handleData = (data: any) => { clearTimeout(timeout); - this.connection!.off('data', handleData); + this.connection!.off("data", handleData); try { - const response = typeof data === 'string' ? JSON.parse(data) : data; + const response = + typeof data === "string" ? JSON.parse(data) : data; resolve(response as AgentResponse); } catch (error) { - reject(new Error('Failed to parse peer response')); + reject(new Error("Failed to parse peer response")); } }; - this.connection.on('data', handleData); + this.connection.on("data", handleData); } else { clearTimeout(timeout); - reject(new Error('Peer connection not available')); + reject(new Error("Peer connection not available")); } }); } @@ -152,18 +163,18 @@ export class AgentClient { // Health check method async health(): Promise<{ status: string }> { - if (this.connectionType === 'peer') { - return { status: this.peer?.open ? 'connected' : 'disconnected' }; + if (this.connectionType === "peer") { + return { status: this.peer?.open ? "connected" : "disconnected" }; } - + try { const response = await fetch(`${this.url}/health`); if (response.ok) { - return { status: 'healthy' }; + return { status: "healthy" }; } - return { status: 'unhealthy' }; + return { status: "unhealthy" }; } catch { - return { status: 'unreachable' }; + return { status: "unreachable" }; } } diff --git a/libs/typescript/agent/tsdown.config.ts b/libs/typescript/agent/tsdown.config.ts index 408990c1..2310390c 100644 --- a/libs/typescript/agent/tsdown.config.ts +++ b/libs/typescript/agent/tsdown.config.ts @@ -1,8 +1,10 @@ -import { defineConfig } from 'tsdown' +import { defineConfig } from "tsdown"; export default defineConfig({ - entry: ['src/index.ts'], - format: ['esm'], + entry: ["src/index.ts"], + format: ["module"], + platform: "browser", dts: true, clean: true, -}) + noExternal: ['peerjs'] +}); diff --git a/libs/typescript/package.json b/libs/typescript/package.json index 76d313ec..530b3b0e 100644 --- a/libs/typescript/package.json +++ b/libs/typescript/package.json @@ -23,7 +23,10 @@ "pnpm": { "onlyBuiltDependencies": [ "@biomejs/biome", - "esbuild" + "esbuild", + "protobufjs", + "sharp", + "unrs-resolver" ] } } \ No newline at end of file diff --git a/libs/typescript/pnpm-lock.yaml b/libs/typescript/pnpm-lock.yaml index 99485af8..24368ff4 100644 --- a/libs/typescript/pnpm-lock.yaml +++ b/libs/typescript/pnpm-lock.yaml @@ -12,6 +12,40 @@ importers: specifier: ^1.9.4 version: 1.9.4 + agent: + dependencies: + '@trycua/core': + specifier: ^0.1.2 + version: 0.1.2 + peerjs: + specifier: ^1.5.4 + version: 1.5.5 + pino: + specifier: ^9.7.0 + version: 9.7.0 + devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@types/node': + specifier: ^22.15.17 + version: 22.15.34 + bumpp: + specifier: ^10.1.0 + version: 10.2.0 + happy-dom: + specifier: ^17.4.7 + version: 17.6.3 + tsdown: + specifier: ^0.14.1 + version: 0.14.1(typescript@5.8.3) + typescript: + specifier: ^5.7.2 + version: 5.8.3 + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@22.15.34)(happy-dom@17.6.3) + computer: dependencies: '@trycua/core': @@ -101,6 +135,10 @@ packages: resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -114,10 +152,19 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/types@7.27.7': resolution: {integrity: sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + '@biomejs/biome@1.9.4': resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} @@ -174,108 +221,219 @@ packages: '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + '@emnapi/core@1.4.5': + resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} + '@emnapi/runtime@1.4.3': resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + '@emnapi/runtime@1.4.5': + resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} + '@emnapi/wasi-threads@1.0.2': resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + '@emnapi/wasi-threads@1.0.4': + resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.5': resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.5': resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.5': resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.5': resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.5': resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.5': resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.5': resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.5': resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.5': resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.5': resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.5': resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.5': resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.5': resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.5': resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.5': resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.5': resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} engines: {node: '>=18'} @@ -288,6 +446,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.5': resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} engines: {node: '>=18'} @@ -300,30 +464,60 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.5': resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.5': resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.5': resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.5': resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.5': resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} engines: {node: '>=18'} @@ -333,6 +527,9 @@ packages: '@jridgewell/gen-mapping@0.3.10': resolution: {integrity: sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -343,76 +540,166 @@ packages: '@jridgewell/trace-mapping@0.3.27': resolution: {integrity: sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==} + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@msgpack/msgpack@2.8.0': + resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} + engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@0.2.11': resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + '@napi-rs/wasm-runtime@1.0.3': + resolution: {integrity: sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q==} + + '@oxc-project/runtime@0.82.2': + resolution: {integrity: sha512-cYxcj5CPn/vo5QSpCZcYzBiLidU5+GlFSqIeNaMgBDtcVRBsBJHZg3pHw999W6nHamFQ1EHuPPByB26tjaJiJw==} + engines: {node: '>=6.9.0'} + '@oxc-project/types@0.70.0': resolution: {integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==} + '@oxc-project/types@0.82.2': + resolution: {integrity: sha512-WMGSwd9FsNBs/WfqIOH0h3k1LBdjZJQGYjGnC+vla/fh6HUsu5HzGPerRljiq1hgMQ6gs031YJR12VyP57b/hQ==} + '@quansync/fs@0.1.3': resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} engines: {node: '>=20.0.0'} + '@rolldown/binding-android-arm64@1.0.0-beta.33': + resolution: {integrity: sha512-xhDQXKftRkEULIxCddrKMR8y0YO/Y+6BKk/XrQP2B29YjV2wr8DByoEz+AHX9BfLHb2srfpdN46UquBW2QXWpQ==} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.33': + resolution: {integrity: sha512-7lhhY08v5ZtRq8JJQaJ49fnJombAPnqllKKCDLU/UvaqNAOEyTGC8J1WVOLC4EA4zbXO5U3CCRgVGyAFNH2VtQ==} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': resolution: {integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.33': + resolution: {integrity: sha512-U2iGjcDV7NWyYyhap8YuY0nwrLX6TvX/9i7gBtdEMPm9z3wIUVGNMVdGlA43uqg7xDpRGpEqGnxbeDgiEwYdnA==} + cpu: [x64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.9': resolution: {integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==} cpu: [x64] os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-beta.33': + resolution: {integrity: sha512-gd6ASromVHFLlzrjJWMG5CXHkS7/36DEZ8HhvGt2NN8eZALCIuyEx8HMMLqvKA7z4EAztVkdToVrdxpGMsKZxw==} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': resolution: {integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==} cpu: [x64] os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.33': + resolution: {integrity: sha512-xmeLfkfGthuynO1EpCdyTVr0r4G+wqvnKCuyR6rXOet+hLrq5HNAC2XtP/jU2TB4Bc6aiLYxl868B8CGtFDhcw==} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': resolution: {integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.33': + resolution: {integrity: sha512-cHGp8yfHL4pes6uaLbO5L58ceFkUK4efd8iE86jClD1QPPDLKiqEXJCFYeuK3OfODuF5EBOmf0SlcUZNEYGdmw==} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': resolution: {integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.33': + resolution: {integrity: sha512-wZ1t7JAvVeFgskH1L9y7c47ITitPytpL0s8FmAT8pVfXcaTmS58ZyoXT+y6cz8uCkQnETjrX3YezTGI18u3ecg==} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': resolution: {integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==} cpu: [arm64] os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.33': + resolution: {integrity: sha512-cDndWo3VEYbm7yeujOV6Ie2XHz0K8YX/R/vbNmMo03m1QwtBKKvbYNSyJb3B9+8igltDjd8zNM9mpiNNrq/ekQ==} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': resolution: {integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==} cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-beta.33': + resolution: {integrity: sha512-bl7uzi6es/l6LT++NZcBpiX43ldLyKXCPwEZGY1rZJ99HQ7m1g3KxWwYCcGxtKjlb2ExVvDZicF6k+96vxOJKg==} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': resolution: {integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==} cpu: [x64] os: [linux] + '@rolldown/binding-openharmony-arm64@1.0.0-beta.33': + resolution: {integrity: sha512-TrgzQanpLgcmmzolCbYA9BPZgF1gYxkIGZhU/HROnJPsq67gcyaYw/JBLioqQLjIwMipETkn25YY799D2OZzJA==} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.33': + resolution: {integrity: sha512-z0LltdUfvoKak9SuaLz/M9AVSg+RTOZjFksbZXzC6Svl1odyW4ai21VHhZy3m2Faeeb/rl/9efVLayj+qYEGxw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': resolution: {integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==} engines: {node: '>=14.21.3'} cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.33': + resolution: {integrity: sha512-CpvOHyqDNOYx9riD4giyXQDIu72bWRU2Dwt1xFSPlBudk6NumK0OJl6Ch+LPnkp5podQHcQg0mMauAXPVKct7g==} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': resolution: {integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.33': + resolution: {integrity: sha512-/tNTvZTWHz6HiVuwpR3zR0kGIyCNb+/tFhnJmti+Aw2fAXs3l7Aj0DcXd0646eFKMX8L2w5hOW9H08FXTUkN0g==} + cpu: [ia32] + os: [win32] + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': resolution: {integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==} cpu: [ia32] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.33': + resolution: {integrity: sha512-Bb2qK3z7g2mf4zaKRvkohHzweaP1lLbaoBmXZFkY6jJWMm0Z8Pfnh8cOoRlH1IVM1Ufbo8ZZ1WXp1LbOpRMtXw==} + cpu: [x64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': resolution: {integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==} cpu: [x64] os: [win32] + '@rolldown/pluginutils@1.0.0-beta.33': + resolution: {integrity: sha512-she25NCG6NoEPC/SEB4pHs5STcnfI4VBFOzjeI63maSPrWME5J2XC8ogrBgp8NaE/xzj28/kbpSaebiMvFRj+w==} + '@rolldown/pluginutils@1.0.0-beta.9': resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} @@ -519,6 +806,9 @@ packages: '@trycua/core@0.1.2': resolution: {integrity: sha512-pSQZaR46OG3MtUCBaneG6RpJD1xfX754VDZ101FM5tkUUiymIrxpQicQEUfhwEBxbI/EmBnmCnVY1AFKvykKzQ==} + '@tybys/wasm-util@0.10.0': + resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -540,9 +830,23 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -554,18 +858,33 @@ packages: vite: optional: true + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} @@ -584,6 +903,10 @@ packages: resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} engines: {node: '>=20.18.0'} + ast-kit@2.1.2: + resolution: {integrity: sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g==} + engines: {node: '>=20.18.0'} + atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -591,6 +914,9 @@ packages: birpc@2.4.0: resolution: {integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==} + birpc@2.5.0: + resolution: {integrity: sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==} + bumpp@10.2.0: resolution: {integrity: sha512-1EJ2NG3M3WYJj4m+GtcxNH6Y7zMQ8q68USMoUGKjM6qFTVXSXCnTxcQSUDV7j4KjLVbk2uK6345Z+6RKOv0w5A==} engines: {node: '>=18'} @@ -670,9 +996,18 @@ packages: resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} engines: {node: '>=14'} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -685,6 +1020,9 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} @@ -770,6 +1108,9 @@ packages: package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -777,6 +1118,14 @@ packages: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} + peerjs-js-binarypack@2.1.0: + resolution: {integrity: sha512-YIwCC+pTzp3Bi8jPI9UFKO0t0SLo6xALnHkiNt/iUFmUUZG0fEEmEyFKvjsDKweiFitzHRyhuh6NvyJZ4nNxMg==} + engines: {node: '>= 14.0.0'} + + peerjs@1.5.5: + resolution: {integrity: sha512-viMUCPDL6CSfOu0ZqVcFqbWRXNHIbv2lPqNbrBIjbFYrflebOjItJ4hPfhjnuUCstqciHVu9vVJ7jFqqKi/EuQ==} + engines: {node: '>= 14'} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -847,6 +1196,26 @@ packages: vue-tsc: optional: true + rolldown-plugin-dts@0.15.7: + resolution: {integrity: sha512-BpdrnLaa+uyw0rPT47+4FUC7hQFazBFppeFT0ioW5Ybg0XCLeRohc3HHPlnCxI6LtzgSWT7Ot8ahn6ji10IQBg==} + engines: {node: '>=20.18.0'} + peerDependencies: + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' + rolldown: ^1.0.0-beta.9 + typescript: ^5.0.0 + vue-tsc: ~3.0.3 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-beta.33: + resolution: {integrity: sha512-mgu118ZuRguC8unhPCbdZbyRbjQfEMiWqlojBA5aRIncBelRaBomnHNpGKYkYWeK7twRz5Cql30xgqqrA3Xelw==} + hasBin: true + rolldown@1.0.0-beta.9: resolution: {integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==} hasBin: true @@ -865,6 +1234,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + sdp@3.2.1: + resolution: {integrity: sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==} + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -913,14 +1285,26 @@ packages: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.3: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + tsdown@0.11.13: resolution: {integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==} engines: {node: '>=18.0.0'} @@ -940,6 +1324,28 @@ packages: unplugin-unused: optional: true + tsdown@0.14.1: + resolution: {integrity: sha512-/nBuFDKZeYln9hAxwWG5Cm55/823sNIVI693iVi0xRFHzf9OVUq4b/lx9PH1TErFr/IQ0kd2hutFbJIPM0XQWA==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -963,11 +1369,47 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite@5.4.19: + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@7.0.0: resolution: {integrity: sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1008,6 +1450,31 @@ packages: yaml: optional: true + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1040,6 +1507,10 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webrtc-adapter@9.0.3: + resolution: {integrity: sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==} + engines: {node: '>=6.0.0', npm: '>=3.10.0'} + whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} @@ -1076,6 +1547,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.27 jsesc: 3.1.0 + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.1.0 + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} @@ -1084,11 +1563,20 @@ snapshots: dependencies: '@babel/types': 7.27.7 + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + '@babel/types@7.27.7': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@biomejs/biome@1.9.4': optionalDependencies: '@biomejs/cli-darwin-arm64': 1.9.4 @@ -1130,88 +1618,173 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.4.5': + dependencies: + '@emnapi/wasi-threads': 1.0.4 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.4.3': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.4.5': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.0.2': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.0.4': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.5': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.5': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.5': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.5': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.5': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.5': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.5': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.5': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.5': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.5': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.5': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.5': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.5': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.5': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.5': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.5': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.5': optional: true '@esbuild/netbsd-arm64@0.25.5': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.5': optional: true '@esbuild/openbsd-arm64@0.25.5': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.5': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.5': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.5': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.5': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.5': optional: true @@ -1220,6 +1793,11 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.2 '@jridgewell/trace-mapping': 0.3.27 + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.5.2': {} @@ -1229,6 +1807,13 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.2 + + '@msgpack/msgpack@2.8.0': {} + '@napi-rs/wasm-runtime@0.2.11': dependencies: '@emnapi/core': 1.4.3 @@ -1236,50 +1821,107 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true + '@napi-rs/wasm-runtime@1.0.3': + dependencies: + '@emnapi/core': 1.4.5 + '@emnapi/runtime': 1.4.5 + '@tybys/wasm-util': 0.10.0 + optional: true + + '@oxc-project/runtime@0.82.2': {} + '@oxc-project/types@0.70.0': {} + '@oxc-project/types@0.82.2': {} + '@quansync/fs@0.1.3': dependencies: quansync: 0.2.10 + '@rolldown/binding-android-arm64@1.0.0-beta.33': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.33': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.33': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.9': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.33': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.33': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.33': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.33': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.33': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.33': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-beta.33': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.33': + dependencies: + '@napi-rs/wasm-runtime': 1.0.3 + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': dependencies: '@napi-rs/wasm-runtime': 0.2.11 optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.33': + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': optional: true + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.33': + optional: true + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.33': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': optional: true + '@rolldown/pluginutils@1.0.0-beta.33': {} + '@rolldown/pluginutils@1.0.0-beta.9': {} '@rollup/rollup-android-arm-eabi@4.44.1': @@ -1349,6 +1991,11 @@ snapshots: posthog-node: 5.1.1 uuid: 11.1.0 + '@tybys/wasm-util@0.10.0': + dependencies: + tslib: 2.8.1 + optional: true + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 @@ -1372,6 +2019,13 @@ snapshots: dependencies: '@types/node': 22.15.34 + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.2.0 + tinyrainbow: 1.2.0 + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -1380,6 +2034,14 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 + '@vitest/mocker@2.1.9(vite@5.4.19(@types/node@22.15.34))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.4.19(@types/node@22.15.34) + '@vitest/mocker@3.2.4(vite@7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 @@ -1388,26 +2050,51 @@ snapshots: optionalDependencies: vite: 7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.17 + pathe: 1.1.2 + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.1.4 + tinyrainbow: 1.2.0 + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -1425,10 +2112,17 @@ snapshots: '@babel/parser': 7.27.7 pathe: 2.0.3 + ast-kit@2.1.2: + dependencies: + '@babel/parser': 7.28.3 + pathe: 2.0.3 + atomic-sleep@1.0.0: {} birpc@2.4.0: {} + birpc@2.5.0: {} + bumpp@10.2.0: dependencies: ansis: 4.1.0 @@ -1502,8 +2196,36 @@ snapshots: empathic@1.1.0: {} + empathic@2.0.0: {} + es-module-lexer@1.7.0: {} + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -1538,6 +2260,8 @@ snapshots: dependencies: '@types/estree': 1.0.8 + eventemitter3@4.0.7: {} + expect-type@1.2.1: {} exsolve@1.0.7: {} @@ -1605,10 +2329,21 @@ snapshots: package-manager-detector@1.3.0: {} + pathe@1.1.2: {} + pathe@2.0.3: {} pathval@2.0.1: {} + peerjs-js-binarypack@2.1.0: {} + + peerjs@1.5.5: + dependencies: + '@msgpack/msgpack': 2.8.0 + eventemitter3: 4.0.7 + peerjs-js-binarypack: 2.1.0 + webrtc-adapter: 9.0.3 + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -1683,6 +2418,45 @@ snapshots: - oxc-resolver - supports-color + rolldown-plugin-dts@0.15.7(rolldown@1.0.0-beta.33)(typescript@5.8.3): + dependencies: + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + ast-kit: 2.1.2 + birpc: 2.5.0 + debug: 4.4.1 + dts-resolver: 2.1.1 + get-tsconfig: 4.10.1 + rolldown: 1.0.0-beta.33 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - oxc-resolver + - supports-color + + rolldown@1.0.0-beta.33: + dependencies: + '@oxc-project/runtime': 0.82.2 + '@oxc-project/types': 0.82.2 + '@rolldown/pluginutils': 1.0.0-beta.33 + ansis: 4.1.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.33 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.33 + '@rolldown/binding-darwin-x64': 1.0.0-beta.33 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.33 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.33 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.33 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.33 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.33 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.33 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.33 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.33 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.33 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.33 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.33 + rolldown@1.0.0-beta.9: dependencies: '@oxc-project/types': 0.70.0 @@ -1730,6 +2504,8 @@ snapshots: safe-stable-stringify@2.5.0: {} + sdp@3.2.1: {} + semver@7.7.2: {} siginfo@2.0.0: {} @@ -1767,10 +2543,16 @@ snapshots: tinypool@1.1.1: {} + tinyrainbow@1.2.0: {} + tinyrainbow@2.0.0: {} + tinyspy@3.0.2: {} + tinyspy@4.0.3: {} + tree-kill@1.2.2: {} + tsdown@0.11.13(typescript@5.8.3): dependencies: ansis: 4.1.0 @@ -1795,6 +2577,30 @@ snapshots: - supports-color - vue-tsc + tsdown@0.14.1(typescript@5.8.3): + dependencies: + ansis: 4.1.0 + cac: 6.7.14 + chokidar: 4.0.3 + debug: 4.4.1 + diff: 8.0.2 + empathic: 2.0.0 + hookable: 5.5.3 + rolldown: 1.0.0-beta.33 + rolldown-plugin-dts: 0.15.7(rolldown@1.0.0-beta.33)(typescript@5.8.3) + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.14 + tree-kill: 1.2.2 + unconfig: 7.3.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@typescript/native-preview' + - oxc-resolver + - supports-color + - vue-tsc + tslib@2.8.1: optional: true @@ -1818,6 +2624,24 @@ snapshots: uuid@11.1.0: {} + vite-node@2.1.9(@types/node@22.15.34): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.19(@types/node@22.15.34) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@3.2.4(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -1839,6 +2663,15 @@ snapshots: - tsx - yaml + vite@5.4.19(@types/node@22.15.34): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.44.1 + optionalDependencies: + '@types/node': 22.15.34 + fsevents: 2.3.3 + vite@7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.25.5 @@ -1854,6 +2687,42 @@ snapshots: tsx: 4.20.3 yaml: 2.8.0 + vitest@2.1.9(@types/node@22.15.34)(happy-dom@17.6.3): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.19(@types/node@22.15.34)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.19(@types/node@22.15.34) + vite-node: 2.1.9(@types/node@22.15.34) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.15.34 + happy-dom: 17.6.3 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@3.2.4(@types/node@22.15.34)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 @@ -1898,6 +2767,10 @@ snapshots: webidl-conversions@7.0.0: {} + webrtc-adapter@9.0.3: + dependencies: + sdp: 3.2.1 + whatwg-mimetype@3.0.0: {} why-is-node-running@2.3.0: diff --git a/libs/typescript/pnpm-workspace.yaml b/libs/typescript/pnpm-workspace.yaml index aa33c1bc..1e69fa06 100644 --- a/libs/typescript/pnpm-workspace.yaml +++ b/libs/typescript/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - "computer" - "core" + - "agent" From 52f7eda6e7b99461beb53071ac57bebdbb4863ce Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 21 Aug 2025 08:10:54 -0400 Subject: [PATCH 10/31] Updated types --- libs/typescript/agent/src/types.ts | 253 +++++++++++++++++------------ 1 file changed, 151 insertions(+), 102 deletions(-) diff --git a/libs/typescript/agent/src/types.ts b/libs/typescript/agent/src/types.ts index e5c079f0..48a4bc3d 100644 --- a/libs/typescript/agent/src/types.ts +++ b/libs/typescript/agent/src/types.ts @@ -1,3 +1,9 @@ +// #region Request +export type ConnectionType = 'http' | 'https' | 'peer'; +export interface AgentClientOptions { + timeout?: number; + retries?: number; +} // Request types matching the Python proxy API export interface AgentRequest { model: string; @@ -13,100 +19,16 @@ export interface AgentRequest { [key: string]: any; }; } +// #endregion -// Agent message types - can be one of several different message types -export type AgentMessage = - | UserMessage - | AssistantMessage - | ReasoningMessage - | ComputerCallMessage - | ComputerCallOutputMessage; -// User input message -export interface UserMessage { - role: 'user'; - content: string; + +// #region Response +// Response types +export interface AgentResponse { + output: AgentMessage[]; + usage: Usage; } - -// Assistant response message -export interface AssistantMessage { - type: 'message'; - role: 'assistant'; - content: OutputContent[]; -} - -// Reasoning/thinking message -export interface ReasoningMessage { - type: 'reasoning'; - summary: SummaryContent[]; -} - -// Computer action call -export interface ComputerCallMessage { - type: 'computer_call'; - call_id: string; - status: 'completed' | 'failed' | 'pending'; - action: ComputerAction; -} - -// Computer action output (usually screenshot) -export interface ComputerCallOutputMessage { - type: 'computer_call_output'; - call_id: string; - output: InputContent; -} - -// Content types -export interface OutputContent { - type: 'output_text'; - text: string; -} - -export interface SummaryContent { - type: 'summary_text'; - text: string; -} - -export interface InputContent { - type: 'input_image' | 'input_text'; - text?: string; - image_url?: string; -} - -// Computer action types -export type ComputerAction = - | ClickAction - | TypeAction - | KeyAction - | ScrollAction - | WaitAction; - -export interface ClickAction { - type: 'click'; - coordinate: [number, number]; -} - -export interface TypeAction { - type: 'type'; - text: string; -} - -export interface KeyAction { - type: 'key'; - key: string; -} - -export interface ScrollAction { - type: 'scroll'; - coordinate: [number, number]; - direction: 'up' | 'down' | 'left' | 'right'; -} - -export interface WaitAction { - type: 'wait'; - seconds?: number; -} - // Usage information export interface Usage { prompt_tokens: number; @@ -114,17 +36,144 @@ export interface Usage { total_tokens: number; response_cost: number; } +// #endregion -// Response types -export interface AgentResponse { - output: AgentMessage[]; - usage: Usage; + + +// #region Messages +// Agent message types - can be one of several different message types +export type AgentMessage = + | UserMessage + | AssistantMessage + | ReasoningMessage + | ComputerCallMessage + | ComputerCallOutputMessage; +// Input message +export interface UserMessage { + type?: 'message'; + role: 'user' | 'system' | 'developer'; + content: string | InputContent[]; } - -// Connection types -export type ConnectionType = 'http' | 'https' | 'peer'; - -export interface AgentClientOptions { - timeout?: number; - retries?: number; +// Output message +export interface AssistantMessage { + type: 'message'; + role: 'assistant'; + content: OutputContent[]; } +// Output reasoning/thinking message +export interface ReasoningMessage { + type: 'reasoning'; + summary: SummaryContent[]; +} +// Output computer action call +export interface ComputerCallMessage { + type: 'computer_call'; + call_id: string; + status: 'completed' | 'failed' | 'pending'; + action: ComputerAction; +} +// Output computer action result (always a screenshot) +export interface ComputerCallOutputMessage { + type: 'computer_call_output'; + call_id: string; + output: ComputerResultContent; +} +// #endregion + + + +// #region Message Content +export interface InputContent { + type: 'input_image' | 'input_text'; + text?: string; + image_url?: string; +} +export interface OutputContent { + type: 'output_text'; + text: string; +} +export interface SummaryContent { + type: 'summary_text'; + text: string; +} +export interface ComputerResultContent { + type: 'computer_screenshot' | 'input_image'; + image_url: string; +} +// #endregion + + + +// #region Actions +export type ComputerAction = + | ComputerActionOpenAI + | ComputerActionAnthropic; +// OpenAI Computer Actions +export type ComputerActionOpenAI = + | ClickAction + | DoubleClickAction + | DragAction + | KeyPressAction + | MoveAction + | ScreenshotAction + | ScrollAction + | TypeAction + | WaitAction; +export interface ClickAction { + type: 'click'; + button: 'left' | 'right' | 'wheel' | 'back' | 'forward'; + x: number; + y: number; +} +export interface DoubleClickAction { + type: 'double_click'; + button?: 'left' | 'right' | 'wheel' | 'back' | 'forward'; + x: number; + y: number; +} +export interface DragAction { + type: 'drag'; + button?: 'left' | 'right' | 'wheel' | 'back' | 'forward'; + path: Array<[number, number]>; +} +export interface KeyPressAction { + type: 'keypress'; + keys: string[]; +} +export interface MoveAction { + type: 'move'; + x: number; + y: number; +} +export interface ScreenshotAction { + type: 'screenshot'; +} +export interface ScrollAction { + type: 'scroll'; + scroll_x: number; + scroll_y: number; + x: number; + y: number; +} +export interface TypeAction { + type: 'type'; + text: string; +} +export interface WaitAction { + type: 'wait'; +} +// Anthropic Computer Actions +export type ComputerActionAnthropic = + | LeftMouseDownAction + | LeftMouseUpAction; +export interface LeftMouseDownAction { + type: 'left_mouse_down'; + x: number; + y: number; +} +export interface LeftMouseUpAction { + type: 'left_mouse_up'; + x: number; + y: number; +} +// #endregion \ No newline at end of file From dcb584fbb8f661607379231c2ac0e7eaeb1f9a73 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 21 Aug 2025 08:14:54 -0400 Subject: [PATCH 11/31] Added function types --- libs/typescript/agent/src/types.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/typescript/agent/src/types.ts b/libs/typescript/agent/src/types.ts index 48a4bc3d..9d5a7b8d 100644 --- a/libs/typescript/agent/src/types.ts +++ b/libs/typescript/agent/src/types.ts @@ -47,7 +47,9 @@ export type AgentMessage = | AssistantMessage | ReasoningMessage | ComputerCallMessage - | ComputerCallOutputMessage; + | ComputerCallOutputMessage + | FunctionCallMesssage + | FunctionCallOutputMessage; // Input message export interface UserMessage { type?: 'message'; @@ -78,6 +80,20 @@ export interface ComputerCallOutputMessage { call_id: string; output: ComputerResultContent; } +// Output function call +export interface FunctionCallMessage { + type: 'function_call'; + call_id: string; + status: 'completed' | 'failed' | 'pending'; + name: string; + arguments: string; // JSON dict of kwargs +} +// Output function call result (always text) +export interface FunctionCallOutputMessage { + type: 'function_call_output'; + call_id: string; + output: string; +} // #endregion From 6539baf0c53ca07305d61b2af40fb3d34816c208 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Thu, 21 Aug 2025 15:01:48 +0100 Subject: [PATCH 12/31] Fix type bugs, remove extensions, update tsdown config with comment --- libs/typescript/agent/src/client.ts | 2 +- libs/typescript/agent/src/index.ts | 4 ++-- libs/typescript/agent/src/types.ts | 2 +- libs/typescript/agent/tsdown.config.ts | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/typescript/agent/src/client.ts b/libs/typescript/agent/src/client.ts index 5ea073db..02b3d191 100644 --- a/libs/typescript/agent/src/client.ts +++ b/libs/typescript/agent/src/client.ts @@ -4,7 +4,7 @@ import type { AgentResponse, ConnectionType, AgentClientOptions, -} from "./types.js"; +} from "./types"; export class AgentClient { private url: string; diff --git a/libs/typescript/agent/src/index.ts b/libs/typescript/agent/src/index.ts index 32d2a28a..78257ee5 100644 --- a/libs/typescript/agent/src/index.ts +++ b/libs/typescript/agent/src/index.ts @@ -20,10 +20,10 @@ export type { ComputerAction, ClickAction, TypeAction, - KeyAction, + KeyPressAction, ScrollAction, WaitAction, Usage, ConnectionType, AgentClientOptions, -} from './types.js'; +} from './types'; diff --git a/libs/typescript/agent/src/types.ts b/libs/typescript/agent/src/types.ts index 9d5a7b8d..01481d29 100644 --- a/libs/typescript/agent/src/types.ts +++ b/libs/typescript/agent/src/types.ts @@ -48,7 +48,7 @@ export type AgentMessage = | ReasoningMessage | ComputerCallMessage | ComputerCallOutputMessage - | FunctionCallMesssage + | FunctionCallMessage | FunctionCallOutputMessage; // Input message export interface UserMessage { diff --git a/libs/typescript/agent/tsdown.config.ts b/libs/typescript/agent/tsdown.config.ts index 2310390c..b837b6ee 100644 --- a/libs/typescript/agent/tsdown.config.ts +++ b/libs/typescript/agent/tsdown.config.ts @@ -6,5 +6,7 @@ export default defineConfig({ platform: "browser", dts: true, clean: true, + // Remove if we don't need to support including the library via '