hopx-ai 0.1.17__py3-none-any.whl

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

Potentially problematic release.


This version of hopx-ai might be problematic. Click here for more details.

@@ -0,0 +1,199 @@
1
+ """
2
+ Template Building Types
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import List, Dict, Optional, Callable, Any
8
+ from datetime import datetime
9
+
10
+
11
+ class StepType(str, Enum):
12
+ """Step types for template building"""
13
+ FROM = "FROM"
14
+ COPY = "COPY"
15
+ RUN = "RUN"
16
+ ENV = "ENV"
17
+ WORKDIR = "WORKDIR"
18
+ USER = "USER"
19
+ CMD = "CMD"
20
+
21
+
22
+ @dataclass
23
+ class RegistryAuth:
24
+ """Basic registry authentication"""
25
+ username: str
26
+ password: str
27
+
28
+
29
+ @dataclass
30
+ class GCPRegistryAuth:
31
+ """GCP Container Registry authentication"""
32
+ service_account_json: Any # str (file path) or dict
33
+
34
+
35
+ @dataclass
36
+ class AWSRegistryAuth:
37
+ """AWS ECR authentication"""
38
+ access_key_id: str
39
+ secret_access_key: str
40
+ region: str
41
+ session_token: Optional[str] = None
42
+
43
+
44
+ @dataclass
45
+ class Step:
46
+ """Represents a build step"""
47
+ type: StepType
48
+ args: List[str]
49
+ files_hash: Optional[str] = None
50
+ skip_cache: bool = False
51
+ registry_auth: Optional[RegistryAuth] = None
52
+ gcp_auth: Optional[GCPRegistryAuth] = None
53
+ aws_auth: Optional[AWSRegistryAuth] = None
54
+
55
+
56
+ @dataclass
57
+ class CopyOptions:
58
+ """Options for COPY steps"""
59
+ owner: Optional[str] = None
60
+ permissions: Optional[str] = None
61
+
62
+
63
+ class ReadyCheckType(str, Enum):
64
+ """Types of ready checks"""
65
+ PORT = "port"
66
+ URL = "url"
67
+ FILE = "file"
68
+ PROCESS = "process"
69
+ COMMAND = "command"
70
+
71
+
72
+ @dataclass
73
+ class ReadyCheck:
74
+ """Ready check configuration"""
75
+ type: ReadyCheckType
76
+ port: Optional[int] = None
77
+ url: Optional[str] = None
78
+ path: Optional[str] = None
79
+ process_name: Optional[str] = None
80
+ command: Optional[str] = None
81
+ timeout: int = 30000
82
+ interval: int = 2000
83
+
84
+
85
+ @dataclass
86
+ class BuildOptions:
87
+ """Options for building a template"""
88
+ alias: str
89
+ api_key: str
90
+ base_url: str = "https://api.your-domain.com"
91
+ cpu: int = 2
92
+ memory: int = 2048
93
+ disk_gb: int = 10
94
+ skip_cache: bool = False
95
+ context_path: Optional[str] = None
96
+ on_log: Optional[Callable[[Dict[str, Any]], None]] = None
97
+ on_progress: Optional[Callable[[int], None]] = None
98
+
99
+
100
+ @dataclass
101
+ class LogEntry:
102
+ """Log entry from build"""
103
+ timestamp: str
104
+ level: str
105
+ message: str
106
+
107
+
108
+ @dataclass
109
+ class StatusUpdate:
110
+ """Status update from build"""
111
+ status: str
112
+ progress: int
113
+ current_step: Optional[str] = None
114
+
115
+
116
+ @dataclass
117
+ class CreateVMOptions:
118
+ """Options for creating a VM from template"""
119
+ alias: Optional[str] = None
120
+ cpu: Optional[int] = None
121
+ memory: Optional[int] = None
122
+ disk_gb: Optional[int] = None
123
+ env_vars: Optional[Dict[str, str]] = None
124
+
125
+
126
+ @dataclass
127
+ class VM:
128
+ """Represents a VM instance"""
129
+ vm_id: str
130
+ template_id: str
131
+ status: str
132
+ ip: str
133
+ agent_url: str
134
+ started_at: str
135
+ _delete_func: Optional[Callable[[], None]] = None
136
+
137
+ async def delete(self):
138
+ """Delete this VM"""
139
+ if self._delete_func:
140
+ await self._delete_func()
141
+
142
+
143
+ @dataclass
144
+ class BuildResult:
145
+ """Result of a template build"""
146
+ build_id: str
147
+ template_id: str
148
+ duration: int
149
+ _create_vm_func: Optional[Callable[[CreateVMOptions], Any]] = None
150
+
151
+ async def create_vm(self, options: CreateVMOptions = None) -> VM:
152
+ """Create a VM from this template"""
153
+ if self._create_vm_func:
154
+ return await self._create_vm_func(options or CreateVMOptions())
155
+ raise RuntimeError("create_vm function not available")
156
+
157
+
158
+ @dataclass
159
+ class UploadLinkResponse:
160
+ """Response from upload link request"""
161
+ present: bool
162
+ upload_url: Optional[str] = None
163
+ expires_at: Optional[str] = None
164
+ request_id: Optional[str] = None
165
+
166
+
167
+ @dataclass
168
+ class BuildResponse:
169
+ """Response from build trigger"""
170
+ build_id: str
171
+ template_id: str
172
+ status: str
173
+ logs_url: str
174
+ request_id: Optional[str] = None
175
+
176
+
177
+ @dataclass
178
+ class BuildStatusResponse:
179
+ """Response from build status check"""
180
+ build_id: str
181
+ template_id: str
182
+ status: str
183
+ progress: int
184
+ started_at: str
185
+ current_step: Optional[str] = None
186
+ estimated_completion: Optional[str] = None
187
+ error: Optional[str] = None
188
+ request_id: Optional[str] = None
189
+
190
+
191
+ @dataclass
192
+ class LogsResponse:
193
+ """Response from build logs polling"""
194
+ logs: str
195
+ offset: int
196
+ status: str
197
+ complete: bool
198
+ request_id: Optional[str] = None
199
+
hopx_ai/terminal.py ADDED
@@ -0,0 +1,164 @@
1
+ """Interactive terminal access via WebSocket."""
2
+
3
+ import logging
4
+ from typing import Optional, AsyncIterator, Dict, Any
5
+
6
+ try:
7
+ from websockets.client import WebSocketClientProtocol
8
+ WEBSOCKETS_AVAILABLE = True
9
+ except ImportError:
10
+ WEBSOCKETS_AVAILABLE = False
11
+ WebSocketClientProtocol = Any # type: ignore
12
+
13
+ from ._ws_client import WebSocketClient
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class Terminal:
19
+ """
20
+ Interactive terminal resource with PTY support via WebSocket.
21
+
22
+ Provides real-time terminal access to the sandbox for interactive commands.
23
+
24
+ Features:
25
+ - Full PTY support
26
+ - Real-time output streaming
27
+ - Terminal resize support
28
+ - Process exit notifications
29
+
30
+ Example:
31
+ >>> import asyncio
32
+ >>>
33
+ >>> async def interactive_terminal():
34
+ ... sandbox = Sandbox.create(template="code-interpreter")
35
+ ...
36
+ ... # Connect to terminal
37
+ ... async with await sandbox.terminal.connect() as ws:
38
+ ... # Send command
39
+ ... await sandbox.terminal.send_input(ws, "ls -la\\n")
40
+ ...
41
+ ... # Receive output
42
+ ... async for message in sandbox.terminal.iter_output(ws):
43
+ ... if message['type'] == 'output':
44
+ ... print(message['data'], end='')
45
+ ... elif message['type'] == 'exit':
46
+ ... print(f"\\nProcess exited: {message['code']}")
47
+ ... break
48
+ >>>
49
+ >>> asyncio.run(interactive_terminal())
50
+ """
51
+
52
+ def __init__(self, ws_client: WebSocketClient):
53
+ """
54
+ Initialize Terminal resource.
55
+
56
+ Args:
57
+ ws_client: WebSocket client
58
+ """
59
+ if not WEBSOCKETS_AVAILABLE:
60
+ raise ImportError(
61
+ "websockets library is required for terminal features. "
62
+ "Install with: pip install websockets"
63
+ )
64
+
65
+ self._ws_client = ws_client
66
+ logger.debug("Terminal resource initialized")
67
+
68
+ async def connect(
69
+ self,
70
+ *,
71
+ timeout: Optional[int] = 30
72
+ ) -> WebSocketClientProtocol:
73
+ """
74
+ Connect to interactive terminal.
75
+
76
+ Args:
77
+ timeout: Connection timeout in seconds
78
+
79
+ Returns:
80
+ WebSocket connection (use with async context manager)
81
+
82
+ Example:
83
+ >>> async with await sandbox.terminal.connect() as ws:
84
+ ... await sandbox.terminal.send_input(ws, "echo 'Hello'\\n")
85
+ ... async for msg in sandbox.terminal.iter_output(ws):
86
+ ... print(msg['data'], end='')
87
+ ... if msg['type'] == 'exit':
88
+ ... break
89
+ """
90
+ return await self._ws_client.connect("/terminal", timeout=timeout)
91
+
92
+ async def send_input(
93
+ self,
94
+ ws: WebSocketClientProtocol,
95
+ data: str
96
+ ) -> None:
97
+ """
98
+ Send input to terminal.
99
+
100
+ Args:
101
+ ws: WebSocket connection
102
+ data: Input data (include \\n for commands)
103
+
104
+ Example:
105
+ >>> await terminal.send_input(ws, "ls -la\\n")
106
+ >>> await terminal.send_input(ws, "cd /workspace\\n")
107
+ """
108
+ await self._ws_client.send_message(ws, {
109
+ "type": "input",
110
+ "data": data
111
+ })
112
+
113
+ async def resize(
114
+ self,
115
+ ws: WebSocketClientProtocol,
116
+ cols: int,
117
+ rows: int
118
+ ) -> None:
119
+ """
120
+ Resize terminal window.
121
+
122
+ Args:
123
+ ws: WebSocket connection
124
+ cols: Number of columns
125
+ rows: Number of rows
126
+
127
+ Example:
128
+ >>> await terminal.resize(ws, cols=120, rows=40)
129
+ """
130
+ await self._ws_client.send_message(ws, {
131
+ "type": "resize",
132
+ "cols": cols,
133
+ "rows": rows
134
+ })
135
+
136
+ async def iter_output(
137
+ self,
138
+ ws: WebSocketClientProtocol
139
+ ) -> AsyncIterator[Dict[str, Any]]:
140
+ """
141
+ Iterate over terminal output messages.
142
+
143
+ Args:
144
+ ws: WebSocket connection
145
+
146
+ Yields:
147
+ Message dictionaries:
148
+ - {"type": "output", "data": "..."}
149
+ - {"type": "exit", "code": 0}
150
+
151
+ Example:
152
+ >>> async for message in terminal.iter_output(ws):
153
+ ... if message['type'] == 'output':
154
+ ... print(message['data'], end='')
155
+ ... elif message['type'] == 'exit':
156
+ ... print(f"Exit code: {message['code']}")
157
+ ... break
158
+ """
159
+ async for message in self._ws_client.iter_messages(ws):
160
+ yield message
161
+
162
+ def __repr__(self) -> str:
163
+ return f"<Terminal ws_client={self._ws_client}>"
164
+