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.
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.
|