cloudsway-agent 1.0.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.
agent_api/__init__.py ADDED
@@ -0,0 +1,68 @@
1
+ """Production Python SDK for the Managed Agent API."""
2
+
3
+ from agent_api._version import (
4
+ DEFAULT_MAX_RETRIES,
5
+ DEFAULT_STREAM_TIMEOUT,
6
+ DEFAULT_TIMEOUT,
7
+ USER_AGENT,
8
+ __version__,
9
+ )
10
+ from agent_api.async_client import AsyncAgentAPI
11
+ from agent_api.client import AgentAPI
12
+ from agent_api.errors import (
13
+ APIConnectionError,
14
+ APIError,
15
+ APIStatusError,
16
+ AuthenticationError,
17
+ BadRequestError,
18
+ InternalServerError,
19
+ NotFoundError,
20
+ PermissionDeniedError,
21
+ RateLimitError,
22
+ is_retryable_status,
23
+ parse_response_error,
24
+ )
25
+ from agent_api.local_functions import (
26
+ function_call_output_input,
27
+ pending_function_calls,
28
+ run_local_function_handlers,
29
+ )
30
+ from agent_api.pagination import AsyncPage, Page, PageResult
31
+ from agent_api.types import *
32
+
33
+ __all__ = [
34
+ "AgentAPI",
35
+ "AsyncAgentAPI",
36
+ "APIError",
37
+ "APIConnectionError",
38
+ "APIStatusError",
39
+ "AuthenticationError",
40
+ "BadRequestError",
41
+ "InternalServerError",
42
+ "NotFoundError",
43
+ "PermissionDeniedError",
44
+ "RateLimitError",
45
+ "Page",
46
+ "AsyncPage",
47
+ "PageResult",
48
+ "AgentCapabilityPreference",
49
+ "AgentResponse",
50
+ "Model",
51
+ "ModelCapabilities",
52
+ "Preset",
53
+ "PublicTool",
54
+ "ResponseCreateParams",
55
+ "ResponseStreamEvent",
56
+ "ResponseStatus",
57
+ "ToolInvocationResult",
58
+ "DEFAULT_MAX_RETRIES",
59
+ "DEFAULT_STREAM_TIMEOUT",
60
+ "DEFAULT_TIMEOUT",
61
+ "USER_AGENT",
62
+ "is_retryable_status",
63
+ "parse_response_error",
64
+ "function_call_output_input",
65
+ "pending_function_calls",
66
+ "run_local_function_handlers",
67
+ "__version__",
68
+ ]
agent_api/_http.py ADDED
@@ -0,0 +1,392 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import time
5
+ from collections.abc import AsyncIterator, Iterator, Mapping
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from agent_api._utils import build_query
11
+ from agent_api._version import DEFAULT_MAX_RETRIES, DEFAULT_STREAM_TIMEOUT, DEFAULT_TIMEOUT, USER_AGENT
12
+ from agent_api.errors import (
13
+ APIConnectionError,
14
+ APIError,
15
+ APIStatusError,
16
+ RateLimitError,
17
+ is_retryable_status,
18
+ parse_response_error,
19
+ )
20
+ from agent_api.streaming import aiter_sse, iter_sse
21
+
22
+
23
+ class SyncHTTPClient:
24
+ def __init__(
25
+ self,
26
+ *,
27
+ base_url: str,
28
+ api_key: str | None,
29
+ timeout: float,
30
+ stream_timeout: float,
31
+ max_retries: int,
32
+ default_headers: Mapping[str, str],
33
+ http_client: httpx.Client,
34
+ ) -> None:
35
+ self.base_url = base_url
36
+ self.api_key = api_key
37
+ self.timeout = timeout
38
+ self.stream_timeout = stream_timeout
39
+ self.max_retries = max_retries
40
+ self.default_headers = dict(default_headers)
41
+ self._client = http_client
42
+
43
+ def request(
44
+ self,
45
+ method: str,
46
+ path: str,
47
+ body: dict[str, Any] | None,
48
+ *,
49
+ timeout: float | None = None,
50
+ max_retries: int | None = None,
51
+ extra_headers: Mapping[str, str] | None = None,
52
+ ) -> Any:
53
+ response = self._request_response(
54
+ method,
55
+ path,
56
+ body,
57
+ stream=False,
58
+ timeout=timeout,
59
+ max_retries=max_retries,
60
+ extra_headers=extra_headers,
61
+ )
62
+ return response.json()
63
+
64
+ def stream(
65
+ self,
66
+ method: str,
67
+ path: str,
68
+ body: dict[str, Any],
69
+ *,
70
+ timeout: float | None = None,
71
+ max_retries: int | None = None,
72
+ extra_headers: Mapping[str, str] | None = None,
73
+ ) -> Iterator[Any]:
74
+ effective_timeout = timeout if timeout is not None else self.stream_timeout
75
+ headers = self._headers(stream=True, extra=extra_headers)
76
+ retries = self.max_retries if max_retries is None else max_retries
77
+ attempt = 0
78
+ while True:
79
+ try:
80
+ with self._client.stream(
81
+ method,
82
+ self.base_url + path,
83
+ json=body,
84
+ headers=headers,
85
+ timeout=effective_timeout,
86
+ ) as response:
87
+ if not response.is_success:
88
+ try:
89
+ payload: Any = response.json()
90
+ except ValueError:
91
+ payload = response.text
92
+ raise parse_response_error(response, payload)
93
+ yield from iter_sse(response.iter_lines())
94
+ return
95
+ except APIError as exc:
96
+ if attempt >= retries:
97
+ raise
98
+ retryable = isinstance(exc, APIConnectionError) or (
99
+ isinstance(exc, APIStatusError) and exc.status_code is not None and is_retryable_status(exc.status_code)
100
+ )
101
+ if not retryable:
102
+ raise
103
+ attempt += 1
104
+ time.sleep(_retry_delay_seconds(exc, attempt))
105
+ except httpx.TimeoutException as exc:
106
+ raise APIConnectionError(f"Request timed out after {effective_timeout}s") from exc
107
+ except httpx.HTTPError as exc:
108
+ raise APIConnectionError(str(exc)) from exc
109
+
110
+ def _request_response(
111
+ self,
112
+ method: str,
113
+ path: str,
114
+ body: dict[str, Any] | None,
115
+ *,
116
+ stream: bool,
117
+ timeout: float | None,
118
+ max_retries: int | None,
119
+ extra_headers: Mapping[str, str] | None,
120
+ ) -> httpx.Response:
121
+ retries = self.max_retries if max_retries is None else max_retries
122
+ attempt = 0
123
+ while True:
124
+ try:
125
+ return self._request_once(
126
+ method,
127
+ path,
128
+ body,
129
+ stream=stream,
130
+ timeout=timeout,
131
+ extra_headers=extra_headers,
132
+ )
133
+ except APIError as exc:
134
+ if attempt >= retries:
135
+ raise
136
+ retryable = isinstance(exc, APIConnectionError) or (
137
+ isinstance(exc, APIStatusError) and exc.status_code is not None and is_retryable_status(exc.status_code)
138
+ )
139
+ if not retryable:
140
+ raise
141
+ attempt += 1
142
+ time.sleep(_retry_delay_seconds(exc, attempt))
143
+
144
+ def _request_once(
145
+ self,
146
+ method: str,
147
+ path: str,
148
+ body: dict[str, Any] | None,
149
+ *,
150
+ stream: bool,
151
+ timeout: float | None,
152
+ extra_headers: Mapping[str, str] | None,
153
+ ) -> httpx.Response:
154
+ effective_timeout = timeout if timeout is not None else (self.stream_timeout if stream else self.timeout)
155
+ headers = self._headers(stream=stream, extra=extra_headers)
156
+ try:
157
+ if stream:
158
+ request = self._client.build_request(
159
+ method,
160
+ self.base_url + path,
161
+ json=body,
162
+ headers=headers,
163
+ timeout=effective_timeout,
164
+ )
165
+ response = self._client.send(request, stream=True)
166
+ else:
167
+ response = self._client.request(
168
+ method,
169
+ self.base_url + path,
170
+ json=body,
171
+ headers=headers,
172
+ timeout=effective_timeout,
173
+ )
174
+ except httpx.TimeoutException as exc:
175
+ raise APIConnectionError(f"Request timed out after {effective_timeout}s") from exc
176
+ except httpx.HTTPError as exc:
177
+ raise APIConnectionError(str(exc)) from exc
178
+
179
+ if response.is_success:
180
+ return response
181
+ try:
182
+ payload: Any = response.json()
183
+ except ValueError:
184
+ payload = response.text
185
+ raise parse_response_error(response, payload)
186
+
187
+ def _headers(self, *, stream: bool, extra: Mapping[str, str] | None) -> dict[str, str]:
188
+ headers = {
189
+ "Accept": "text/event-stream" if stream else "application/json",
190
+ "User-Agent": USER_AGENT,
191
+ **self.default_headers,
192
+ **dict(extra or {}),
193
+ }
194
+ if self.api_key:
195
+ headers["Authorization"] = f"Bearer {self.api_key}"
196
+ return headers
197
+
198
+
199
+ class AsyncHTTPClient:
200
+ def __init__(
201
+ self,
202
+ *,
203
+ base_url: str,
204
+ api_key: str | None,
205
+ timeout: float,
206
+ stream_timeout: float,
207
+ max_retries: int,
208
+ default_headers: Mapping[str, str],
209
+ http_client: httpx.AsyncClient,
210
+ ) -> None:
211
+ self.base_url = base_url
212
+ self.api_key = api_key
213
+ self.timeout = timeout
214
+ self.stream_timeout = stream_timeout
215
+ self.max_retries = max_retries
216
+ self.default_headers = dict(default_headers)
217
+ self._client = http_client
218
+
219
+ async def request(
220
+ self,
221
+ method: str,
222
+ path: str,
223
+ body: dict[str, Any] | None,
224
+ *,
225
+ timeout: float | None = None,
226
+ max_retries: int | None = None,
227
+ extra_headers: Mapping[str, str] | None = None,
228
+ ) -> Any:
229
+ response = await self._request_response(
230
+ method,
231
+ path,
232
+ body,
233
+ stream=False,
234
+ timeout=timeout,
235
+ max_retries=max_retries,
236
+ extra_headers=extra_headers,
237
+ )
238
+ return response.json()
239
+
240
+ async def stream(
241
+ self,
242
+ method: str,
243
+ path: str,
244
+ body: dict[str, Any],
245
+ *,
246
+ timeout: float | None = None,
247
+ max_retries: int | None = None,
248
+ extra_headers: Mapping[str, str] | None = None,
249
+ ) -> AsyncIterator[Any]:
250
+ effective_timeout = timeout if timeout is not None else self.stream_timeout
251
+ headers = self._headers(stream=True, extra=extra_headers)
252
+ retries = self.max_retries if max_retries is None else max_retries
253
+ attempt = 0
254
+ while True:
255
+ try:
256
+ async with self._client.stream(
257
+ method,
258
+ self.base_url + path,
259
+ json=body,
260
+ headers=headers,
261
+ timeout=effective_timeout,
262
+ ) as response:
263
+ if not response.is_success:
264
+ try:
265
+ payload: Any = response.json()
266
+ except ValueError:
267
+ payload = response.text
268
+ raise parse_response_error(response, payload)
269
+ async for event in aiter_sse(response.aiter_lines()):
270
+ yield event
271
+ return
272
+ except APIError as exc:
273
+ if attempt >= retries:
274
+ raise
275
+ retryable = isinstance(exc, APIConnectionError) or (
276
+ isinstance(exc, APIStatusError) and exc.status_code is not None and is_retryable_status(exc.status_code)
277
+ )
278
+ if not retryable:
279
+ raise
280
+ attempt += 1
281
+ await _async_sleep(_retry_delay_seconds(exc, attempt))
282
+ except httpx.TimeoutException as exc:
283
+ raise APIConnectionError(f"Request timed out after {effective_timeout}s") from exc
284
+ except httpx.HTTPError as exc:
285
+ raise APIConnectionError(str(exc)) from exc
286
+
287
+ async def _request_response(
288
+ self,
289
+ method: str,
290
+ path: str,
291
+ body: dict[str, Any] | None,
292
+ *,
293
+ stream: bool,
294
+ timeout: float | None,
295
+ max_retries: int | None,
296
+ extra_headers: Mapping[str, str] | None,
297
+ ) -> httpx.Response:
298
+ retries = self.max_retries if max_retries is None else max_retries
299
+ attempt = 0
300
+ while True:
301
+ try:
302
+ return await self._request_once(
303
+ method,
304
+ path,
305
+ body,
306
+ stream=stream,
307
+ timeout=timeout,
308
+ extra_headers=extra_headers,
309
+ )
310
+ except APIError as exc:
311
+ if attempt >= retries:
312
+ raise
313
+ retryable = isinstance(exc, APIConnectionError) or (
314
+ isinstance(exc, APIStatusError) and exc.status_code is not None and is_retryable_status(exc.status_code)
315
+ )
316
+ if not retryable:
317
+ raise
318
+ attempt += 1
319
+ await _async_sleep(_retry_delay_seconds(exc, attempt))
320
+
321
+ async def _request_once(
322
+ self,
323
+ method: str,
324
+ path: str,
325
+ body: dict[str, Any] | None,
326
+ *,
327
+ stream: bool,
328
+ timeout: float | None,
329
+ extra_headers: Mapping[str, str] | None,
330
+ ) -> httpx.Response:
331
+ effective_timeout = timeout if timeout is not None else (self.stream_timeout if stream else self.timeout)
332
+ headers = self._headers(stream=stream, extra=extra_headers)
333
+ try:
334
+ if stream:
335
+ request = self._client.build_request(
336
+ method,
337
+ self.base_url + path,
338
+ json=body,
339
+ headers=headers,
340
+ timeout=effective_timeout,
341
+ )
342
+ response = await self._client.send(request, stream=True)
343
+ else:
344
+ response = await self._client.request(
345
+ method,
346
+ self.base_url + path,
347
+ json=body,
348
+ headers=headers,
349
+ timeout=effective_timeout,
350
+ )
351
+ except httpx.TimeoutException as exc:
352
+ raise APIConnectionError(f"Request timed out after {effective_timeout}s") from exc
353
+ except httpx.HTTPError as exc:
354
+ raise APIConnectionError(str(exc)) from exc
355
+
356
+ if response.is_success:
357
+ return response
358
+ try:
359
+ payload: Any = response.json()
360
+ except ValueError:
361
+ payload = response.text
362
+ raise parse_response_error(response, payload)
363
+
364
+ def _headers(self, *, stream: bool, extra: Mapping[str, str] | None) -> dict[str, str]:
365
+ headers = {
366
+ "Accept": "text/event-stream" if stream else "application/json",
367
+ "User-Agent": USER_AGENT,
368
+ **self.default_headers,
369
+ **dict(extra or {}),
370
+ }
371
+ if self.api_key:
372
+ headers["Authorization"] = f"Bearer {self.api_key}"
373
+ return headers
374
+
375
+
376
+ def _retry_delay_seconds(error: APIError, attempt: int) -> float:
377
+ if isinstance(error, RateLimitError) and error.retry_after_seconds is not None:
378
+ return error.retry_after_seconds
379
+ base = 0.25
380
+ cap = 8.0
381
+ exponential = min(cap, base * (2 ** (attempt - 1)))
382
+ jitter = random.uniform(0, 0.1)
383
+ return exponential + jitter
384
+
385
+
386
+ async def _async_sleep(seconds: float) -> None:
387
+ import asyncio
388
+
389
+ await asyncio.sleep(seconds)
390
+
391
+
392
+ __all__ = ["SyncHTTPClient", "AsyncHTTPClient"]
agent_api/_utils.py ADDED
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from agent_api.types import AgentResponse
6
+
7
+
8
+ def drop_none(values: dict[str, Any]) -> dict[str, Any]:
9
+ return {key: value for key, value in values.items() if value is not None}
10
+
11
+
12
+ def add_output_text(response: AgentResponse) -> AgentResponse:
13
+ if "output_text" in response:
14
+ return response
15
+ text = ""
16
+ for item in response.get("output", []):
17
+ if item.get("type") != "message":
18
+ continue
19
+ for part in item.get("content", []):
20
+ if part.get("type") == "output_text":
21
+ text += part.get("text", "")
22
+ response["output_text"] = text
23
+ return response
24
+
25
+
26
+ def build_query(params: dict[str, Any]) -> str:
27
+ filtered = {key: value for key, value in params.items() if value is not None and value != ""}
28
+ if not filtered:
29
+ return ""
30
+ from urllib.parse import urlencode
31
+
32
+ return "?" + urlencode(filtered)
agent_api/_version.py ADDED
@@ -0,0 +1,8 @@
1
+ """Agent API Python SDK version."""
2
+
3
+ __version__ = "1.0.0"
4
+ USER_AGENT = f"cloudsway-agent/{__version__}"
5
+
6
+ DEFAULT_TIMEOUT = 600.0
7
+ DEFAULT_STREAM_TIMEOUT = 3600.0
8
+ DEFAULT_MAX_RETRIES = 2
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from collections.abc import Mapping
5
+
6
+ import httpx
7
+
8
+ from agent_api._http import AsyncHTTPClient
9
+ from agent_api._version import DEFAULT_MAX_RETRIES, DEFAULT_STREAM_TIMEOUT, DEFAULT_TIMEOUT
10
+ from agent_api.resources.catalog import AsyncModelsAPI, AsyncPresetsAPI, AsyncToolsAPI
11
+ from agent_api.resources.responses import AsyncResponsesAPI
12
+
13
+
14
+ class AsyncAgentAPI:
15
+ """Asynchronous production client for the Managed Agent API."""
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ api_key: str | None = None,
21
+ base_url: str | None = None,
22
+ timeout: float | httpx.Timeout | None = None,
23
+ stream_timeout: float | None = None,
24
+ max_retries: int | None = None,
25
+ default_headers: Mapping[str, str] | None = None,
26
+ http_client: httpx.AsyncClient | None = None,
27
+ ) -> None:
28
+ self.api_key = api_key or os.environ.get("AGENT_API_KEY")
29
+ self.base_url = (base_url or os.environ.get("AGENT_API_BASE_URL") or "https://api.agentsway.dev").rstrip("/")
30
+ self.timeout = float(timeout) if isinstance(timeout, (int, float)) else DEFAULT_TIMEOUT
31
+ self.stream_timeout = stream_timeout if stream_timeout is not None else DEFAULT_STREAM_TIMEOUT
32
+ self.max_retries = max_retries if max_retries is not None else DEFAULT_MAX_RETRIES
33
+ self.default_headers = dict(default_headers or {})
34
+ self._client = http_client or httpx.AsyncClient(timeout=timeout or DEFAULT_TIMEOUT)
35
+ self._http = AsyncHTTPClient(
36
+ base_url=self.base_url,
37
+ api_key=self.api_key,
38
+ timeout=self.timeout,
39
+ stream_timeout=self.stream_timeout,
40
+ max_retries=self.max_retries,
41
+ default_headers=self.default_headers,
42
+ http_client=self._client,
43
+ )
44
+ self.responses = AsyncResponsesAPI(self._http, "/v1/responses")
45
+ self.agent = AsyncResponsesAPI(self._http, "/v1/agent")
46
+ self.models = AsyncModelsAPI(self._http)
47
+ self.presets = AsyncPresetsAPI(self._http)
48
+ self.tools = AsyncToolsAPI(self._http)
49
+
50
+ async def close(self) -> None:
51
+ await self._client.aclose()
52
+
53
+ async def __aenter__(self) -> AsyncAgentAPI:
54
+ return self
55
+
56
+ async def __aexit__(self, *_exc: object) -> None:
57
+ await self.close()
agent_api/client.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from collections.abc import Mapping
5
+
6
+ import httpx
7
+
8
+ from agent_api._http import SyncHTTPClient
9
+ from agent_api._version import DEFAULT_MAX_RETRIES, DEFAULT_STREAM_TIMEOUT, DEFAULT_TIMEOUT
10
+ from agent_api.resources.catalog import ModelsAPI, PresetsAPI, ToolsAPI
11
+ from agent_api.resources.responses import ResponsesAPI
12
+
13
+
14
+ class AgentAPI:
15
+ """Synchronous production client for the Managed Agent API."""
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ api_key: str | None = None,
21
+ base_url: str | None = None,
22
+ timeout: float | httpx.Timeout | None = None,
23
+ stream_timeout: float | None = None,
24
+ max_retries: int | None = None,
25
+ default_headers: Mapping[str, str] | None = None,
26
+ http_client: httpx.Client | None = None,
27
+ ) -> None:
28
+ self.api_key = api_key or os.environ.get("AGENT_API_KEY")
29
+ self.base_url = (base_url or os.environ.get("AGENT_API_BASE_URL") or "https://api.agentsway.dev").rstrip("/")
30
+ self.timeout = float(timeout) if isinstance(timeout, (int, float)) else DEFAULT_TIMEOUT
31
+ self.stream_timeout = stream_timeout if stream_timeout is not None else DEFAULT_STREAM_TIMEOUT
32
+ self.max_retries = max_retries if max_retries is not None else DEFAULT_MAX_RETRIES
33
+ self.default_headers = dict(default_headers or {})
34
+ self._client = http_client or httpx.Client(timeout=timeout or DEFAULT_TIMEOUT)
35
+ self._http = SyncHTTPClient(
36
+ base_url=self.base_url,
37
+ api_key=self.api_key,
38
+ timeout=self.timeout,
39
+ stream_timeout=self.stream_timeout,
40
+ max_retries=self.max_retries,
41
+ default_headers=self.default_headers,
42
+ http_client=self._client,
43
+ )
44
+ self.responses = ResponsesAPI(self._http, "/v1/responses")
45
+ self.agent = ResponsesAPI(self._http, "/v1/agent")
46
+ self.models = ModelsAPI(self._http)
47
+ self.presets = PresetsAPI(self._http)
48
+ self.tools = ToolsAPI(self._http)
49
+
50
+ def close(self) -> None:
51
+ self._client.close()
52
+
53
+ def __enter__(self) -> AgentAPI:
54
+ return self
55
+
56
+ def __exit__(self, *_exc: object) -> None:
57
+ self.close()
58
+