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.
@@ -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})"