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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.