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.
ai_computer/__init__.py
ADDED
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,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.
|