ai-computer-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ from .client import SandboxClient, SandboxResponse, StreamEvent
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["SandboxClient", "SandboxResponse", "StreamEvent"]
ai_computer/client.py ADDED
@@ -0,0 +1,265 @@
1
+ import aiohttp
2
+ import json
3
+ import asyncio
4
+ from typing import Optional, Dict, AsyncGenerator, Union
5
+ from dataclasses import dataclass
6
+
7
+ @dataclass
8
+ class SandboxResponse:
9
+ """Response from sandbox operations.
10
+
11
+ Attributes:
12
+ success: Whether the operation was successful
13
+ data: Optional response data
14
+ error: Optional error message if operation failed
15
+ """
16
+ success: bool
17
+ data: Optional[Dict] = None
18
+ error: Optional[str] = None
19
+
20
+ @dataclass
21
+ class StreamEvent:
22
+ """Event from streaming code execution.
23
+
24
+ Attributes:
25
+ type: Type of event ('stdout', 'stderr', 'info', 'error', 'completed', 'keepalive')
26
+ data: Event data
27
+ """
28
+ type: str
29
+ data: str
30
+
31
+ class SandboxClient:
32
+ """Client for interacting with the AI Sandbox service.
33
+
34
+ This client provides methods to execute Python code in an isolated sandbox environment.
35
+ It handles authentication, sandbox creation/deletion, and code execution.
36
+
37
+ Args:
38
+ base_url: The base URL of the sandbox service
39
+ token: Optional pre-existing authentication token
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ base_url: str = "http://aicomputer.dev",
45
+ token: Optional[str] = None
46
+ ):
47
+ self.base_url = base_url.rstrip('/')
48
+ self.token = token
49
+ self.sandbox_id = None
50
+
51
+ async def setup(self) -> SandboxResponse:
52
+ """Initialize the client and create a sandbox.
53
+
54
+ This method:
55
+ 1. Gets a development token (if not provided)
56
+ 2. Creates a new sandbox
57
+ 3. Waits for the sandbox to be ready
58
+
59
+ Returns:
60
+ SandboxResponse indicating success/failure
61
+ """
62
+ async with aiohttp.ClientSession() as session:
63
+ # Get development token if not provided
64
+ if not self.token:
65
+ async with session.post(f"{self.base_url}/dev/token") as response:
66
+ if response.status == 200:
67
+ data = await response.json()
68
+ self.token = data["access_token"]
69
+ else:
70
+ text = await response.text()
71
+ return SandboxResponse(success=False, error=text)
72
+
73
+ # Create sandbox
74
+ headers = {"Authorization": f"Bearer {self.token}"}
75
+ async with session.post(f"{self.base_url}/api/v1/sandbox/create", headers=headers) as response:
76
+ if response.status == 200:
77
+ data = await response.json()
78
+ self.sandbox_id = data["sandbox_id"]
79
+ # Wait for sandbox to be ready
80
+ ready = await self.wait_for_ready()
81
+ if not ready.success:
82
+ return ready
83
+ return SandboxResponse(success=True, data=data)
84
+ else:
85
+ text = await response.text()
86
+ return SandboxResponse(success=False, error=text)
87
+
88
+ async def wait_for_ready(self, max_retries: int = 30, delay: int = 1) -> SandboxResponse:
89
+ """Wait for the sandbox to be in Running state.
90
+
91
+ Args:
92
+ max_retries: Maximum number of status check attempts
93
+ delay: Delay between retries in seconds
94
+
95
+ Returns:
96
+ SandboxResponse indicating if sandbox is ready
97
+ """
98
+ if not self.token or not self.sandbox_id:
99
+ return SandboxResponse(success=False, error="Client not properly initialized")
100
+
101
+ headers = {"Authorization": f"Bearer {self.token}"}
102
+
103
+ for _ in range(max_retries):
104
+ async with aiohttp.ClientSession() as session:
105
+ async with session.get(
106
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}/status",
107
+ headers=headers
108
+ ) as response:
109
+ if response.status == 200:
110
+ data = await response.json()
111
+ if data["status"] == "Running":
112
+ return SandboxResponse(success=True, data=data)
113
+ await asyncio.sleep(delay)
114
+
115
+ return SandboxResponse(success=False, error="Sandbox failed to become ready")
116
+
117
+ async def execute_code(
118
+ self,
119
+ code: Union[str, bytes],
120
+ timeout: int = 30
121
+ ) -> SandboxResponse:
122
+ """Execute Python code in the sandbox and return the combined output.
123
+
124
+ This method collects all output from the streaming response and returns it as a single result.
125
+ It captures both stdout and stderr, and handles any errors during execution.
126
+
127
+ Args:
128
+ code: Python code to execute
129
+ timeout: Maximum execution time in seconds
130
+
131
+ Returns:
132
+ SandboxResponse containing execution results
133
+ """
134
+ if not self.token or not self.sandbox_id:
135
+ return SandboxResponse(success=False, error="Client not properly initialized. Call setup() first")
136
+
137
+ # Ensure sandbox is ready
138
+ ready = await self.wait_for_ready()
139
+ if not ready.success:
140
+ return ready
141
+
142
+ headers = {
143
+ "Authorization": f"Bearer {self.token}",
144
+ "Content-Type": "application/json"
145
+ }
146
+
147
+ data = {
148
+ "code": code,
149
+ "timeout": timeout
150
+ }
151
+
152
+ try:
153
+ async with aiohttp.ClientSession() as session:
154
+ async with session.post(
155
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}/execute",
156
+ headers=headers,
157
+ json=data
158
+ ) as response:
159
+ if response.status != 200:
160
+ error_text = await response.text()
161
+ return SandboxResponse(success=False, error=error_text)
162
+
163
+ # Parse the response
164
+ result = await response.json()
165
+ return SandboxResponse(success=True, data=result)
166
+
167
+ except Exception as e:
168
+ return SandboxResponse(success=False, error=f"Connection error: {str(e)}")
169
+
170
+ async def execute_code_stream(
171
+ self,
172
+ code: Union[str, bytes],
173
+ timeout: int = 30
174
+ ) -> AsyncGenerator[StreamEvent, None]:
175
+ """Execute Python code in the sandbox and stream the output.
176
+
177
+ This method returns an async generator that yields StreamEvent objects containing
178
+ the type of event and the associated data.
179
+
180
+ Args:
181
+ code: Python code to execute
182
+ timeout: Maximum execution time in seconds
183
+
184
+ Yields:
185
+ StreamEvent objects with execution output/events
186
+ """
187
+ if not self.token or not self.sandbox_id:
188
+ yield StreamEvent(type="error", data="Client not properly initialized. Call setup() first")
189
+ return
190
+
191
+ # Ensure sandbox is ready
192
+ ready = await self.wait_for_ready()
193
+ if not ready.success:
194
+ yield StreamEvent(type="error", data=ready.error or "Sandbox not ready")
195
+ return
196
+
197
+ headers = {
198
+ "Authorization": f"Bearer {self.token}",
199
+ "Content-Type": "application/json"
200
+ }
201
+
202
+ data = {
203
+ "code": code,
204
+ "timeout": timeout
205
+ }
206
+
207
+ try:
208
+ # Create a ClientTimeout object with all timeout settings
209
+ timeout_settings = aiohttp.ClientTimeout(
210
+ total=timeout + 30, # Add buffer for connection overhead
211
+ connect=30,
212
+ sock_connect=30,
213
+ sock_read=timeout + 30
214
+ )
215
+
216
+ async with aiohttp.ClientSession(timeout=timeout_settings) as session:
217
+ async with session.post(
218
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}/execute/stream",
219
+ headers=headers,
220
+ json=data
221
+ ) as response:
222
+ if response.status != 200:
223
+ error_text = await response.text()
224
+ yield StreamEvent(type="error", data=error_text)
225
+ return
226
+
227
+ # Process the streaming response
228
+ async for line in response.content:
229
+ if line:
230
+ try:
231
+ event = json.loads(line.decode())
232
+ yield StreamEvent(type=event['type'], data=event['data'])
233
+
234
+ # Stop if we receive an error or completed event
235
+ if event['type'] in ['error', 'completed']:
236
+ break
237
+ except json.JSONDecodeError as e:
238
+ yield StreamEvent(type="error", data=f"Failed to parse event: {str(e)}")
239
+ break
240
+
241
+ except Exception as e:
242
+ yield StreamEvent(type="error", data=f"Connection error: {str(e)}")
243
+
244
+ async def cleanup(self) -> SandboxResponse:
245
+ """Delete the sandbox.
246
+
247
+ Returns:
248
+ SandboxResponse indicating success/failure of cleanup
249
+ """
250
+ if not self.token or not self.sandbox_id:
251
+ return SandboxResponse(success=True)
252
+
253
+ headers = {"Authorization": f"Bearer {self.token}"}
254
+ async with aiohttp.ClientSession() as session:
255
+ async with session.delete(
256
+ f"{self.base_url}/api/v1/sandbox/{self.sandbox_id}",
257
+ headers=headers
258
+ ) as response:
259
+ if response.status == 200:
260
+ data = await response.json()
261
+ self.sandbox_id = None
262
+ return SandboxResponse(success=True, data=data)
263
+ else:
264
+ text = await response.text()
265
+ return SandboxResponse(success=False, error=text)
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-computer-client
3
+ Version: 0.1.0
4
+ Summary: Python client for interacting with the AI Computer service
5
+ Project-URL: Homepage, https://github.com/ColeMurray/ai-computer-client-python
6
+ Project-URL: Documentation, https://github.com/ColeMurray/ai-computer-client-python#readme
7
+ Author: AI Computer
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Requires-Python: >=3.7
20
+ Requires-Dist: aiohttp>=3.8.0
21
+ Requires-Dist: typing-extensions>=4.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
25
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # AI Computer Python Client
29
+
30
+ A Python client for interacting with the AI Computer service. This client provides a simple interface for executing Python code in an isolated sandbox environment.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install ai-computer-client
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ import asyncio
42
+ from ai_computer import SandboxClient
43
+
44
+ async def main():
45
+ # Initialize the client
46
+ client = SandboxClient()
47
+
48
+ # Setup the client (gets token and creates sandbox)
49
+ setup_response = await client.setup()
50
+ if not setup_response.success:
51
+ print(f"Setup failed: {setup_response.error}")
52
+ return
53
+
54
+ try:
55
+ # Execute some code
56
+ code = """
57
+ print('Hello from AI Computer!')
58
+ result = 42
59
+ print(f'The answer is {result}')
60
+ """
61
+
62
+ # Stream execution
63
+ async for event in client.execute_code_stream(code):
64
+ if event.type == 'stdout':
65
+ print(f"Output: {event.data}")
66
+ elif event.type == 'stderr':
67
+ print(f"Error: {event.data}")
68
+ elif event.type == 'error':
69
+ print(f"Execution error: {event.data}")
70
+ break
71
+ elif event.type == 'completed':
72
+ print("Execution completed")
73
+ break
74
+
75
+ finally:
76
+ # Clean up
77
+ await client.cleanup()
78
+
79
+ if __name__ == "__main__":
80
+ asyncio.run(main())
81
+ ```
82
+
83
+ ## Features
84
+
85
+ - Asynchronous API
86
+ - Streaming execution output
87
+ - Automatic sandbox management
88
+ - Error handling and timeouts
89
+ - Type hints for better IDE support
90
+
91
+ ## API Reference
92
+
93
+ ### SandboxClient
94
+
95
+ The main client class for interacting with the AI Computer service.
96
+
97
+ #### Methods
98
+
99
+ - `setup()`: Initialize the client and create a sandbox
100
+ - `execute_code(code: str, timeout: int = 30)`: Execute code and return combined output
101
+ - `execute_code_stream(code: str, timeout: int = 30)`: Execute code and stream output
102
+ - `cleanup()`: Delete the sandbox
103
+ - `wait_for_ready()`: Wait for sandbox to be ready
104
+
105
+ ## Development
106
+
107
+ ### Running Tests
108
+
109
+ ```bash
110
+ pip install -e ".[dev]"
111
+ pytest
112
+ ```
113
+
114
+ ### Contributing
115
+
116
+ 1. Fork the repository
117
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
118
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
119
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
120
+ 5. Open a Pull Request
121
+
122
+ ## License
123
+
124
+ MIT License
@@ -0,0 +1,6 @@
1
+ ai_computer/__init__.py,sha256=g2yDiDT6i24MV3Y2JNfk2ZFkYvxvrN6NXywjZPslXDY,149
2
+ ai_computer/client.py,sha256=PaBRmSFzgOXNxxQAd3zSCaqzZ8rcvUOhsnFulu40uhc,10214
3
+ ai_computer_client-0.1.0.dist-info/METADATA,sha256=2kn41kUgglJ8RSC2NOHPLh_rs_O6SXbiUdUUm64OhEY,3487
4
+ ai_computer_client-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ ai_computer_client-0.1.0.dist-info/licenses/LICENSE,sha256=N_0S5G1Wik2LWVDViJMAM0Z-6vTBX1bvDjb8vouBA-c,1068
6
+ ai_computer_client-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 AI Computer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.