computer-agents 2.2.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.
- computer_agents/__init__.py +198 -0
- computer_agents/_api_client.py +296 -0
- computer_agents/_exceptions.py +32 -0
- computer_agents/client.py +272 -0
- computer_agents/py.typed +0 -0
- computer_agents/resources/__init__.py +29 -0
- computer_agents/resources/agents.py +106 -0
- computer_agents/resources/budget.py +210 -0
- computer_agents/resources/environments.py +332 -0
- computer_agents/resources/files.py +141 -0
- computer_agents/resources/git.py +70 -0
- computer_agents/resources/orchestrations.py +140 -0
- computer_agents/resources/projects.py +60 -0
- computer_agents/resources/runs.py +133 -0
- computer_agents/resources/schedules.py +144 -0
- computer_agents/resources/threads.py +281 -0
- computer_agents/resources/triggers.py +150 -0
- computer_agents/types.py +915 -0
- computer_agents-2.2.0.dist-info/METADATA +204 -0
- computer_agents-2.2.0.dist-info/RECORD +21 -0
- computer_agents-2.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Computer Agents SDK - Official Python client for the Computer Agents Cloud API.
|
|
2
|
+
|
|
3
|
+
Execute Claude-powered AI agents in isolated cloud containers.
|
|
4
|
+
|
|
5
|
+
Example::
|
|
6
|
+
|
|
7
|
+
from computer_agents import ComputerAgentsClient
|
|
8
|
+
|
|
9
|
+
client = ComputerAgentsClient(api_key="ca_...")
|
|
10
|
+
|
|
11
|
+
# Execute a task
|
|
12
|
+
result = client.run(
|
|
13
|
+
"Create a REST API",
|
|
14
|
+
environment_id="env_xxx",
|
|
15
|
+
on_event=lambda e: print(e["type"]),
|
|
16
|
+
)
|
|
17
|
+
print(result.content)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
__version__ = "2.2.0"
|
|
21
|
+
|
|
22
|
+
# ============================================================================
|
|
23
|
+
# Main Client
|
|
24
|
+
# ============================================================================
|
|
25
|
+
|
|
26
|
+
from .client import ComputerAgentsClient, RunResult
|
|
27
|
+
from ._exceptions import ApiClientError
|
|
28
|
+
from ._api_client import ApiClient
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# Resource Managers (for advanced usage)
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
from .resources import (
|
|
35
|
+
AgentsResource,
|
|
36
|
+
BillingResource,
|
|
37
|
+
BudgetResource,
|
|
38
|
+
EnvironmentsResource,
|
|
39
|
+
FilesResource,
|
|
40
|
+
GitResource,
|
|
41
|
+
OrchestrationsResource,
|
|
42
|
+
ProjectsResource,
|
|
43
|
+
RunsResource,
|
|
44
|
+
SchedulesResource,
|
|
45
|
+
SendMessageResult,
|
|
46
|
+
ThreadsResource,
|
|
47
|
+
TriggersResource,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# ============================================================================
|
|
51
|
+
# Types
|
|
52
|
+
# ============================================================================
|
|
53
|
+
|
|
54
|
+
from .types import (
|
|
55
|
+
# Common
|
|
56
|
+
PaginationParams,
|
|
57
|
+
ApiErrorBody,
|
|
58
|
+
|
|
59
|
+
# Projects
|
|
60
|
+
Project,
|
|
61
|
+
CreateProjectParams,
|
|
62
|
+
UpdateProjectParams,
|
|
63
|
+
ProjectStats,
|
|
64
|
+
|
|
65
|
+
# Environments
|
|
66
|
+
Environment,
|
|
67
|
+
CreateEnvironmentParams,
|
|
68
|
+
UpdateEnvironmentParams,
|
|
69
|
+
EnvironmentVariable,
|
|
70
|
+
McpServer,
|
|
71
|
+
RuntimeConfig,
|
|
72
|
+
PackagesConfig,
|
|
73
|
+
AvailableRuntimes,
|
|
74
|
+
ContainerStatus,
|
|
75
|
+
BuildResult,
|
|
76
|
+
BuildStatusResult,
|
|
77
|
+
BuildLogsResult,
|
|
78
|
+
TestBuildResult,
|
|
79
|
+
DockerfileResult,
|
|
80
|
+
ValidateDockerfileResult,
|
|
81
|
+
InstallPackagesResult,
|
|
82
|
+
StartContainerParams,
|
|
83
|
+
StartContainerResult,
|
|
84
|
+
|
|
85
|
+
# Threads
|
|
86
|
+
Thread,
|
|
87
|
+
CreateThreadParams,
|
|
88
|
+
UpdateThreadParams,
|
|
89
|
+
ListThreadsParams,
|
|
90
|
+
SendMessageParams,
|
|
91
|
+
ThreadMessage,
|
|
92
|
+
AgentConfig,
|
|
93
|
+
CopyThreadParams,
|
|
94
|
+
SearchThreadsParams,
|
|
95
|
+
SearchThreadResult,
|
|
96
|
+
SearchThreadsResponse,
|
|
97
|
+
ThreadLogEntry,
|
|
98
|
+
ResearchSession,
|
|
99
|
+
|
|
100
|
+
# Stream Events
|
|
101
|
+
StreamEvent,
|
|
102
|
+
MessageStreamEvent,
|
|
103
|
+
ResponseStartedEvent,
|
|
104
|
+
ResponseItemCompletedEvent,
|
|
105
|
+
ResponseCompletedEvent,
|
|
106
|
+
StreamCompletedEvent,
|
|
107
|
+
StreamErrorEvent,
|
|
108
|
+
|
|
109
|
+
# Runs
|
|
110
|
+
Run,
|
|
111
|
+
CreateRunParams,
|
|
112
|
+
UpdateRunParams,
|
|
113
|
+
ListRunsParams,
|
|
114
|
+
RunLogEntry,
|
|
115
|
+
RunDiff,
|
|
116
|
+
TokenUsage,
|
|
117
|
+
|
|
118
|
+
# Agents
|
|
119
|
+
CloudAgent,
|
|
120
|
+
CreateAgentParams,
|
|
121
|
+
UpdateAgentParams,
|
|
122
|
+
AgentBinary,
|
|
123
|
+
|
|
124
|
+
# Budget & Billing
|
|
125
|
+
BudgetStatus,
|
|
126
|
+
CanExecuteResult,
|
|
127
|
+
IncreaseBudgetParams,
|
|
128
|
+
IncreaseBudgetResult,
|
|
129
|
+
BillingRecord,
|
|
130
|
+
ListBillingRecordsParams,
|
|
131
|
+
BillingAccount,
|
|
132
|
+
UsageStats,
|
|
133
|
+
UsageStatsParams,
|
|
134
|
+
|
|
135
|
+
# Files
|
|
136
|
+
FileEntry,
|
|
137
|
+
ListFilesParams,
|
|
138
|
+
UploadFileParams,
|
|
139
|
+
CreateDirectoryParams,
|
|
140
|
+
|
|
141
|
+
# Git
|
|
142
|
+
GitDiffFile,
|
|
143
|
+
GitDiffResult,
|
|
144
|
+
GitCommitParams,
|
|
145
|
+
GitCommitResult,
|
|
146
|
+
GitPushParams,
|
|
147
|
+
GitPushResult,
|
|
148
|
+
|
|
149
|
+
# Schedules
|
|
150
|
+
Schedule,
|
|
151
|
+
CreateScheduleParams,
|
|
152
|
+
UpdateScheduleParams,
|
|
153
|
+
|
|
154
|
+
# Triggers
|
|
155
|
+
Trigger,
|
|
156
|
+
CreateTriggerParams,
|
|
157
|
+
UpdateTriggerParams,
|
|
158
|
+
TriggerAction,
|
|
159
|
+
TriggerExecution,
|
|
160
|
+
|
|
161
|
+
# Orchestrations
|
|
162
|
+
Orchestration,
|
|
163
|
+
CreateOrchestrationParams,
|
|
164
|
+
UpdateOrchestrationParams,
|
|
165
|
+
OrchestrationStep,
|
|
166
|
+
OrchestrationStepResult,
|
|
167
|
+
OrchestrationRun,
|
|
168
|
+
|
|
169
|
+
# Health
|
|
170
|
+
HealthCheck,
|
|
171
|
+
Metrics,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
__all__ = [
|
|
175
|
+
# Version
|
|
176
|
+
"__version__",
|
|
177
|
+
|
|
178
|
+
# Client
|
|
179
|
+
"ComputerAgentsClient",
|
|
180
|
+
"RunResult",
|
|
181
|
+
"ApiClientError",
|
|
182
|
+
"ApiClient",
|
|
183
|
+
|
|
184
|
+
# Resources
|
|
185
|
+
"AgentsResource",
|
|
186
|
+
"BillingResource",
|
|
187
|
+
"BudgetResource",
|
|
188
|
+
"EnvironmentsResource",
|
|
189
|
+
"FilesResource",
|
|
190
|
+
"GitResource",
|
|
191
|
+
"OrchestrationsResource",
|
|
192
|
+
"ProjectsResource",
|
|
193
|
+
"RunsResource",
|
|
194
|
+
"SchedulesResource",
|
|
195
|
+
"SendMessageResult",
|
|
196
|
+
"ThreadsResource",
|
|
197
|
+
"TriggersResource",
|
|
198
|
+
]
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""Low-level HTTP client for the Computer Agents Cloud API.
|
|
2
|
+
|
|
3
|
+
Handles authentication, request/response processing, SSE streaming,
|
|
4
|
+
and error handling. Higher-level resource managers use this client.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any, Generator, Iterator
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from ._exceptions import ApiClientError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
DEFAULT_BASE_URL = "https://api.computer-agents.com"
|
|
18
|
+
DEFAULT_TIMEOUT = 60.0 # seconds
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ApiClient:
|
|
22
|
+
"""Low-level HTTP client for the Computer Agents API.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
api_key: API key for authentication.
|
|
26
|
+
base_url: Base URL for the API. Defaults to ``https://api.computer-agents.com``.
|
|
27
|
+
timeout: Request timeout in seconds. Defaults to 60.
|
|
28
|
+
debug: Enable debug logging. Defaults to False.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
api_key: str,
|
|
34
|
+
base_url: str | None = None,
|
|
35
|
+
timeout: float | None = None,
|
|
36
|
+
debug: bool = False,
|
|
37
|
+
) -> None:
|
|
38
|
+
if not api_key:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
"API key is required. Provide it via:\n"
|
|
41
|
+
'1. Constructor: ApiClient(api_key="...")\n'
|
|
42
|
+
"2. Environment variable: COMPUTER_AGENTS_API_KEY"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
self._api_key = api_key
|
|
46
|
+
self._base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
47
|
+
self._timeout = timeout or DEFAULT_TIMEOUT
|
|
48
|
+
self._debug = debug
|
|
49
|
+
|
|
50
|
+
self._client = httpx.Client(
|
|
51
|
+
base_url=self._base_url,
|
|
52
|
+
timeout=httpx.Timeout(self._timeout, connect=10.0),
|
|
53
|
+
headers={
|
|
54
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def close(self) -> None:
|
|
59
|
+
"""Close the underlying HTTP client."""
|
|
60
|
+
self._client.close()
|
|
61
|
+
|
|
62
|
+
def __enter__(self) -> "ApiClient":
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def __exit__(self, *args: Any) -> None:
|
|
66
|
+
self.close()
|
|
67
|
+
|
|
68
|
+
# =========================================================================
|
|
69
|
+
# Core request methods
|
|
70
|
+
# =========================================================================
|
|
71
|
+
|
|
72
|
+
def request(
|
|
73
|
+
self,
|
|
74
|
+
method: str,
|
|
75
|
+
path: str,
|
|
76
|
+
*,
|
|
77
|
+
body: Any | None = None,
|
|
78
|
+
query: dict[str, Any] | None = None,
|
|
79
|
+
headers: dict[str, str] | None = None,
|
|
80
|
+
timeout: float | None = None,
|
|
81
|
+
) -> Any:
|
|
82
|
+
"""Make an HTTP request to the API and return parsed JSON."""
|
|
83
|
+
# Filter out None values from query params
|
|
84
|
+
params = None
|
|
85
|
+
if query:
|
|
86
|
+
params = {k: v for k, v in query.items() if v is not None}
|
|
87
|
+
|
|
88
|
+
request_headers: dict[str, str] = {}
|
|
89
|
+
if headers:
|
|
90
|
+
request_headers.update(headers)
|
|
91
|
+
if body is not None and "Content-Type" not in request_headers:
|
|
92
|
+
request_headers["Content-Type"] = "application/json"
|
|
93
|
+
|
|
94
|
+
if self._debug:
|
|
95
|
+
print(f"[ApiClient] {method} {self._base_url}{path}")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = self._client.request(
|
|
99
|
+
method,
|
|
100
|
+
path,
|
|
101
|
+
json=body if body is not None else None,
|
|
102
|
+
params=params,
|
|
103
|
+
headers=request_headers,
|
|
104
|
+
timeout=timeout or self._timeout,
|
|
105
|
+
)
|
|
106
|
+
except httpx.TimeoutException:
|
|
107
|
+
raise ApiClientError(
|
|
108
|
+
f"Request timeout after {timeout or self._timeout}s",
|
|
109
|
+
408,
|
|
110
|
+
"TIMEOUT",
|
|
111
|
+
)
|
|
112
|
+
except httpx.HTTPError as e:
|
|
113
|
+
raise ApiClientError(str(e), 500, "NETWORK_ERROR")
|
|
114
|
+
|
|
115
|
+
if not response.is_success:
|
|
116
|
+
raise self._parse_error(response)
|
|
117
|
+
|
|
118
|
+
# Handle 204 No Content
|
|
119
|
+
if response.status_code == 204:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
return response.json()
|
|
123
|
+
|
|
124
|
+
def request_stream(
|
|
125
|
+
self,
|
|
126
|
+
method: str,
|
|
127
|
+
path: str,
|
|
128
|
+
*,
|
|
129
|
+
body: Any | None = None,
|
|
130
|
+
timeout: float | None = None,
|
|
131
|
+
) -> Iterator[dict[str, Any]]:
|
|
132
|
+
"""Make a streaming SSE request and yield parsed events."""
|
|
133
|
+
headers = {
|
|
134
|
+
"Accept": "text/event-stream",
|
|
135
|
+
}
|
|
136
|
+
if body is not None:
|
|
137
|
+
headers["Content-Type"] = "application/json"
|
|
138
|
+
|
|
139
|
+
content = json.dumps(body).encode() if body is not None else None
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
with self._client.stream(
|
|
143
|
+
method,
|
|
144
|
+
path,
|
|
145
|
+
content=content,
|
|
146
|
+
headers=headers,
|
|
147
|
+
timeout=timeout or 600.0, # 10 minutes for streaming
|
|
148
|
+
) as response:
|
|
149
|
+
if not response.is_success:
|
|
150
|
+
# Read error body
|
|
151
|
+
response.read()
|
|
152
|
+
raise self._parse_error(response)
|
|
153
|
+
|
|
154
|
+
yield from self._parse_sse(response.iter_lines())
|
|
155
|
+
except httpx.TimeoutException:
|
|
156
|
+
raise ApiClientError(
|
|
157
|
+
f"Stream timeout after {timeout or 600.0}s",
|
|
158
|
+
408,
|
|
159
|
+
"TIMEOUT",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def request_raw(
|
|
163
|
+
self,
|
|
164
|
+
method: str,
|
|
165
|
+
path: str,
|
|
166
|
+
*,
|
|
167
|
+
timeout: float | None = None,
|
|
168
|
+
) -> httpx.Response:
|
|
169
|
+
"""Make a raw HTTP request and return the response object."""
|
|
170
|
+
try:
|
|
171
|
+
response = self._client.request(
|
|
172
|
+
method,
|
|
173
|
+
path,
|
|
174
|
+
timeout=timeout or self._timeout,
|
|
175
|
+
)
|
|
176
|
+
except httpx.TimeoutException:
|
|
177
|
+
raise ApiClientError(
|
|
178
|
+
f"Request timeout after {timeout or self._timeout}s",
|
|
179
|
+
408,
|
|
180
|
+
"TIMEOUT",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if not response.is_success:
|
|
184
|
+
raise self._parse_error(response)
|
|
185
|
+
|
|
186
|
+
return response
|
|
187
|
+
|
|
188
|
+
def request_form(
|
|
189
|
+
self,
|
|
190
|
+
method: str,
|
|
191
|
+
path: str,
|
|
192
|
+
*,
|
|
193
|
+
data: dict[str, Any] | None = None,
|
|
194
|
+
files: dict[str, Any] | None = None,
|
|
195
|
+
timeout: float | None = None,
|
|
196
|
+
) -> Any:
|
|
197
|
+
"""Make a multipart form request."""
|
|
198
|
+
try:
|
|
199
|
+
response = self._client.request(
|
|
200
|
+
method,
|
|
201
|
+
path,
|
|
202
|
+
data=data,
|
|
203
|
+
files=files,
|
|
204
|
+
timeout=timeout or self._timeout,
|
|
205
|
+
)
|
|
206
|
+
except httpx.TimeoutException:
|
|
207
|
+
raise ApiClientError(
|
|
208
|
+
f"Request timeout after {timeout or self._timeout}s",
|
|
209
|
+
408,
|
|
210
|
+
"TIMEOUT",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if not response.is_success:
|
|
214
|
+
raise self._parse_error(response)
|
|
215
|
+
|
|
216
|
+
return response.json()
|
|
217
|
+
|
|
218
|
+
# =========================================================================
|
|
219
|
+
# Convenience methods
|
|
220
|
+
# =========================================================================
|
|
221
|
+
|
|
222
|
+
def get(
|
|
223
|
+
self,
|
|
224
|
+
path: str,
|
|
225
|
+
query: dict[str, Any] | None = None,
|
|
226
|
+
) -> Any:
|
|
227
|
+
return self.request("GET", path, query=query)
|
|
228
|
+
|
|
229
|
+
def post(
|
|
230
|
+
self,
|
|
231
|
+
path: str,
|
|
232
|
+
body: Any | None = None,
|
|
233
|
+
) -> Any:
|
|
234
|
+
return self.request("POST", path, body=body)
|
|
235
|
+
|
|
236
|
+
def patch(
|
|
237
|
+
self,
|
|
238
|
+
path: str,
|
|
239
|
+
body: Any | None = None,
|
|
240
|
+
) -> Any:
|
|
241
|
+
return self.request("PATCH", path, body=body)
|
|
242
|
+
|
|
243
|
+
def put(
|
|
244
|
+
self,
|
|
245
|
+
path: str,
|
|
246
|
+
body: Any | None = None,
|
|
247
|
+
) -> Any:
|
|
248
|
+
return self.request("PUT", path, body=body)
|
|
249
|
+
|
|
250
|
+
def delete(self, path: str) -> Any:
|
|
251
|
+
return self.request("DELETE", path)
|
|
252
|
+
|
|
253
|
+
# =========================================================================
|
|
254
|
+
# Accessors
|
|
255
|
+
# =========================================================================
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def base_url(self) -> str:
|
|
259
|
+
return self._base_url
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def api_key(self) -> str:
|
|
263
|
+
return self._api_key
|
|
264
|
+
|
|
265
|
+
# =========================================================================
|
|
266
|
+
# Internal helpers
|
|
267
|
+
# =========================================================================
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def _parse_sse(lines: Iterator[str]) -> Generator[dict[str, Any], None, None]:
|
|
271
|
+
"""Parse SSE event stream lines into dicts."""
|
|
272
|
+
for line in lines:
|
|
273
|
+
if line.startswith("data: "):
|
|
274
|
+
data_str = line[6:]
|
|
275
|
+
if not data_str.strip():
|
|
276
|
+
continue
|
|
277
|
+
try:
|
|
278
|
+
yield json.loads(data_str)
|
|
279
|
+
except json.JSONDecodeError:
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
def _parse_error(self, response: httpx.Response) -> ApiClientError:
|
|
283
|
+
try:
|
|
284
|
+
error_data = response.json()
|
|
285
|
+
except Exception:
|
|
286
|
+
error_data = {
|
|
287
|
+
"error": response.reason_phrase or "Unknown error",
|
|
288
|
+
"message": f"HTTP {response.status_code}",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return ApiClientError(
|
|
292
|
+
message=error_data.get("message") or error_data.get("error", "Unknown error"),
|
|
293
|
+
status=response.status_code,
|
|
294
|
+
code=error_data.get("code"),
|
|
295
|
+
details=error_data.get("details"),
|
|
296
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Exception classes for the Computer Agents SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ApiClientError(Exception):
|
|
9
|
+
"""Error returned by the Computer Agents API.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
message: Human-readable error message.
|
|
13
|
+
status: HTTP status code.
|
|
14
|
+
code: Machine-readable error code (e.g. ``"TIMEOUT"``).
|
|
15
|
+
details: Additional error context from the API.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
message: str,
|
|
21
|
+
status: int,
|
|
22
|
+
code: str | None = None,
|
|
23
|
+
details: dict[str, Any] | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.message = message
|
|
27
|
+
self.status = status
|
|
28
|
+
self.code = code
|
|
29
|
+
self.details = details
|
|
30
|
+
|
|
31
|
+
def __repr__(self) -> str:
|
|
32
|
+
return f"ApiClientError(message={self.message!r}, status={self.status}, code={self.code!r})"
|