acontext 0.0.1.dev2__tar.gz → 0.0.1.dev4__tar.gz
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.
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/PKG-INFO +3 -2
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/README.md +1 -1
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/pyproject.toml +8 -3
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/__init__.py +19 -1
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/_constants.py +0 -1
- acontext-0.0.1.dev4/src/acontext/_utils.py +42 -0
- acontext-0.0.1.dev4/src/acontext/async_client.py +206 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/client.py +63 -35
- acontext-0.0.1.dev4/src/acontext/client_types.py +36 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/errors.py +2 -2
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/messages.py +2 -10
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/resources/__init__.py +9 -0
- acontext-0.0.1.dev4/src/acontext/resources/async_blocks.py +163 -0
- acontext-0.0.1.dev4/src/acontext/resources/async_disks.py +195 -0
- acontext-0.0.1.dev4/src/acontext/resources/async_sessions.py +272 -0
- acontext-0.0.1.dev4/src/acontext/resources/async_spaces.py +90 -0
- acontext-0.0.1.dev4/src/acontext/resources/blocks.py +162 -0
- acontext-0.0.1.dev4/src/acontext/resources/disks.py +194 -0
- acontext-0.0.1.dev4/src/acontext/resources/sessions.py +271 -0
- acontext-0.0.1.dev4/src/acontext/resources/spaces.py +89 -0
- acontext-0.0.1.dev4/src/acontext/types/__init__.py +54 -0
- acontext-0.0.1.dev4/src/acontext/types/block.py +26 -0
- acontext-0.0.1.dev4/src/acontext/types/disk.py +65 -0
- acontext-0.0.1.dev4/src/acontext/types/session.py +123 -0
- acontext-0.0.1.dev4/src/acontext/types/space.py +24 -0
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/uploads.py +5 -5
- acontext-0.0.1.dev2/src/acontext/client_types.py +0 -21
- acontext-0.0.1.dev2/src/acontext/resources/blocks.py +0 -94
- acontext-0.0.1.dev2/src/acontext/resources/disks.py +0 -109
- acontext-0.0.1.dev2/src/acontext/resources/sessions.py +0 -148
- acontext-0.0.1.dev2/src/acontext/resources/spaces.py +0 -58
- {acontext-0.0.1.dev2 → acontext-0.0.1.dev4}/src/acontext/py.typed +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: acontext
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev4
|
|
4
4
|
Summary: Python SDK for the Acontext API
|
|
5
5
|
Keywords: acontext,sdk,client,api
|
|
6
6
|
Requires-Dist: httpx>=0.28.1
|
|
7
7
|
Requires-Dist: openai>=2.6.1
|
|
8
8
|
Requires-Dist: anthropic>=0.72.0
|
|
9
|
+
Requires-Dist: pydantic>=2.12.3
|
|
9
10
|
Requires-Python: >=3.10
|
|
10
11
|
Project-URL: Homepage, https://github.com/memodb-io/Acontext
|
|
11
12
|
Project-URL: Issues, https://github.com/memodb-io/Acontext/issues
|
|
@@ -63,7 +64,7 @@ try:
|
|
|
63
64
|
content=b"# Retro Notes\nWe shipped file uploads successfully!\n",
|
|
64
65
|
content_type="text/markdown",
|
|
65
66
|
),
|
|
66
|
-
file_path="notes/
|
|
67
|
+
file_path="/notes/",
|
|
67
68
|
meta={"source": "readme-demo"},
|
|
68
69
|
)
|
|
69
70
|
finally:
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acontext"
|
|
3
|
-
version = "0.0.1.
|
|
3
|
+
version = "0.0.1.dev4"
|
|
4
4
|
description = "Python SDK for the Acontext API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"httpx>=0.28.1",
|
|
9
9
|
"openai>=2.6.1",
|
|
10
|
-
"anthropic>=0.72.0"
|
|
10
|
+
"anthropic>=0.72.0",
|
|
11
|
+
"pydantic>=2.12.3",
|
|
11
12
|
]
|
|
12
13
|
keywords = ["acontext", "sdk", "client", "api"]
|
|
13
14
|
|
|
@@ -17,7 +18,11 @@ Repository = "https://github.com/memodb-io/Acontext"
|
|
|
17
18
|
Issues = "https://github.com/memodb-io/Acontext/issues"
|
|
18
19
|
|
|
19
20
|
[dependency-groups]
|
|
20
|
-
dev = [
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest",
|
|
23
|
+
"ruff",
|
|
24
|
+
"pytest-asyncio"
|
|
25
|
+
]
|
|
21
26
|
|
|
22
27
|
[build-system]
|
|
23
28
|
requires = ["uv_build>=0.9.2,<0.10.0"]
|
|
@@ -4,12 +4,25 @@ Python SDK for the Acontext API.
|
|
|
4
4
|
|
|
5
5
|
from importlib import metadata as _metadata
|
|
6
6
|
|
|
7
|
+
from .async_client import AcontextAsyncClient
|
|
7
8
|
from .client import AcontextClient, FileUpload, MessagePart
|
|
8
9
|
from .messages import AcontextMessage
|
|
9
|
-
from .resources import
|
|
10
|
+
from .resources import (
|
|
11
|
+
AsyncBlocksAPI,
|
|
12
|
+
AsyncDiskArtifactsAPI,
|
|
13
|
+
AsyncDisksAPI,
|
|
14
|
+
AsyncSessionsAPI,
|
|
15
|
+
AsyncSpacesAPI,
|
|
16
|
+
BlocksAPI,
|
|
17
|
+
DiskArtifactsAPI,
|
|
18
|
+
DisksAPI,
|
|
19
|
+
SessionsAPI,
|
|
20
|
+
SpacesAPI,
|
|
21
|
+
)
|
|
10
22
|
|
|
11
23
|
__all__ = [
|
|
12
24
|
"AcontextClient",
|
|
25
|
+
"AcontextAsyncClient",
|
|
13
26
|
"FileUpload",
|
|
14
27
|
"MessagePart",
|
|
15
28
|
"AcontextMessage",
|
|
@@ -18,6 +31,11 @@ __all__ = [
|
|
|
18
31
|
"BlocksAPI",
|
|
19
32
|
"SessionsAPI",
|
|
20
33
|
"SpacesAPI",
|
|
34
|
+
"AsyncDisksAPI",
|
|
35
|
+
"AsyncDiskArtifactsAPI",
|
|
36
|
+
"AsyncBlocksAPI",
|
|
37
|
+
"AsyncSessionsAPI",
|
|
38
|
+
"AsyncSpacesAPI",
|
|
21
39
|
"__version__",
|
|
22
40
|
]
|
|
23
41
|
|
|
@@ -5,7 +5,6 @@ Internal constants shared across the Python SDK.
|
|
|
5
5
|
from importlib import metadata as _metadata
|
|
6
6
|
|
|
7
7
|
DEFAULT_BASE_URL = "https://api.acontext.io/api/v1"
|
|
8
|
-
SUPPORTED_ROLES = {"user", "assistant", "system", "tool", "function"}
|
|
9
8
|
|
|
10
9
|
try:
|
|
11
10
|
_VERSION = _metadata.version("acontext-py")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Utility functions for the acontext Python client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def bool_to_str(value: bool) -> str:
|
|
7
|
+
"""Convert a boolean value to string representation used by the API.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
value: The boolean value to convert.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
"true" if value is True, "false" otherwise.
|
|
14
|
+
"""
|
|
15
|
+
return "true" if value else "false"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_params(**kwargs: Any) -> dict[str, Any]:
|
|
19
|
+
"""Build query parameters dictionary, filtering None values and converting booleans.
|
|
20
|
+
|
|
21
|
+
This function filters out None values and converts boolean values to their
|
|
22
|
+
string representations ("true" or "false") as expected by the API.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
**kwargs: Keyword arguments to build parameters from.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Dictionary with non-None parameters, with booleans converted to strings.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> build_params(limit=10, cursor=None, time_desc=True)
|
|
32
|
+
{'limit': 10, 'time_desc': 'true'}
|
|
33
|
+
"""
|
|
34
|
+
params: dict[str, Any] = {}
|
|
35
|
+
for key, value in kwargs.items():
|
|
36
|
+
if value is not None:
|
|
37
|
+
if isinstance(value, bool):
|
|
38
|
+
params[key] = bool_to_str(value)
|
|
39
|
+
else:
|
|
40
|
+
params[key] = value
|
|
41
|
+
return params
|
|
42
|
+
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
High-level asynchronous client for the Acontext API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import Any, BinaryIO
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from ._constants import DEFAULT_BASE_URL, DEFAULT_USER_AGENT
|
|
12
|
+
from .errors import APIError, TransportError
|
|
13
|
+
from .messages import MessagePart as MessagePart
|
|
14
|
+
from .uploads import FileUpload as FileUpload
|
|
15
|
+
from .resources.async_disks import AsyncDisksAPI as AsyncDisksAPI
|
|
16
|
+
from .resources.async_blocks import AsyncBlocksAPI as AsyncBlocksAPI
|
|
17
|
+
from .resources.async_sessions import AsyncSessionsAPI as AsyncSessionsAPI
|
|
18
|
+
from .resources.async_spaces import AsyncSpacesAPI as AsyncSpacesAPI
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AcontextAsyncClient:
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
api_key: str | None = None,
|
|
27
|
+
base_url: str | None = None,
|
|
28
|
+
timeout: float | httpx.Timeout | None = 10.0,
|
|
29
|
+
user_agent: str | None = None,
|
|
30
|
+
client: httpx.AsyncClient | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Initialize the Acontext async client.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
api_key: API key for authentication. Can also be set via ACONTEXT_API_KEY env var.
|
|
37
|
+
base_url: Base URL for the API. Defaults to DEFAULT_BASE_URL. Can also be set via ACONTEXT_BASE_URL env var.
|
|
38
|
+
timeout: Request timeout in seconds. Defaults to 10.0. Can also be set via ACONTEXT_TIMEOUT env var.
|
|
39
|
+
Can also be an httpx.Timeout object.
|
|
40
|
+
user_agent: Custom user agent string. Can also be set via ACONTEXT_USER_AGENT env var.
|
|
41
|
+
client: Optional httpx.AsyncClient instance to reuse. If provided, headers and base_url
|
|
42
|
+
will be merged with the client configuration.
|
|
43
|
+
"""
|
|
44
|
+
# Priority: explicit parameters > environment variables > defaults
|
|
45
|
+
# Load api_key from parameter or environment variable
|
|
46
|
+
api_key = api_key or os.getenv("ACONTEXT_API_KEY")
|
|
47
|
+
if not api_key or not api_key.strip():
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"api_key is required. Provide it either as a parameter (api_key='...') "
|
|
50
|
+
"or set the ACONTEXT_API_KEY environment variable."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Load other parameters from environment variables if not provided
|
|
54
|
+
if base_url is None:
|
|
55
|
+
base_url = os.getenv("ACONTEXT_BASE_URL", DEFAULT_BASE_URL)
|
|
56
|
+
base_url = base_url.rstrip("/")
|
|
57
|
+
|
|
58
|
+
if user_agent is None:
|
|
59
|
+
user_agent = os.getenv("ACONTEXT_USER_AGENT", DEFAULT_USER_AGENT)
|
|
60
|
+
|
|
61
|
+
# Handle timeout: support both float and httpx.Timeout
|
|
62
|
+
if timeout is None:
|
|
63
|
+
timeout_str = os.getenv("ACONTEXT_TIMEOUT")
|
|
64
|
+
if timeout_str:
|
|
65
|
+
try:
|
|
66
|
+
timeout = float(timeout_str)
|
|
67
|
+
except ValueError:
|
|
68
|
+
timeout = 10.0
|
|
69
|
+
else:
|
|
70
|
+
timeout = 10.0
|
|
71
|
+
|
|
72
|
+
# Determine actual timeout value
|
|
73
|
+
actual_timeout: float | httpx.Timeout
|
|
74
|
+
if isinstance(timeout, httpx.Timeout):
|
|
75
|
+
actual_timeout = timeout
|
|
76
|
+
else:
|
|
77
|
+
actual_timeout = float(timeout)
|
|
78
|
+
|
|
79
|
+
headers = {
|
|
80
|
+
"Authorization": f"Bearer {api_key}",
|
|
81
|
+
"Accept": "application/json",
|
|
82
|
+
"User-Agent": user_agent,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if client is not None:
|
|
86
|
+
self._client = client
|
|
87
|
+
self._owns_client = False
|
|
88
|
+
if client.base_url == httpx.URL():
|
|
89
|
+
client.base_url = httpx.URL(base_url)
|
|
90
|
+
for name, value in headers.items():
|
|
91
|
+
if name not in client.headers:
|
|
92
|
+
client.headers[name] = value
|
|
93
|
+
self._base_url = str(client.base_url) or base_url
|
|
94
|
+
else:
|
|
95
|
+
self._client = httpx.AsyncClient(
|
|
96
|
+
base_url=base_url,
|
|
97
|
+
headers=headers,
|
|
98
|
+
timeout=actual_timeout,
|
|
99
|
+
)
|
|
100
|
+
self._owns_client = True
|
|
101
|
+
self._base_url = base_url
|
|
102
|
+
|
|
103
|
+
self._timeout = actual_timeout
|
|
104
|
+
|
|
105
|
+
self.spaces = AsyncSpacesAPI(self)
|
|
106
|
+
self.sessions = AsyncSessionsAPI(self)
|
|
107
|
+
self.disks = AsyncDisksAPI(self)
|
|
108
|
+
self.artifacts = self.disks.artifacts
|
|
109
|
+
self.blocks = AsyncBlocksAPI(self)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def base_url(self) -> str:
|
|
113
|
+
return self._base_url
|
|
114
|
+
|
|
115
|
+
async def aclose(self) -> None:
|
|
116
|
+
"""Close the async client."""
|
|
117
|
+
if self._owns_client:
|
|
118
|
+
await self._client.aclose()
|
|
119
|
+
|
|
120
|
+
async def __aenter__(self) -> "AcontextAsyncClient":
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: D401 - standard context manager protocol
|
|
124
|
+
await self.aclose()
|
|
125
|
+
|
|
126
|
+
# ------------------------------------------------------------------
|
|
127
|
+
# HTTP plumbing shared by resource clients
|
|
128
|
+
# ------------------------------------------------------------------
|
|
129
|
+
async def request(
|
|
130
|
+
self,
|
|
131
|
+
method: str,
|
|
132
|
+
path: str,
|
|
133
|
+
*,
|
|
134
|
+
params: Mapping[str, Any] | None = None,
|
|
135
|
+
json_data: Mapping[str, Any] | None = None,
|
|
136
|
+
data: Mapping[str, Any] | None = None,
|
|
137
|
+
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
138
|
+
unwrap: bool = True,
|
|
139
|
+
) -> Any:
|
|
140
|
+
try:
|
|
141
|
+
response = await self._client.request(
|
|
142
|
+
method=method,
|
|
143
|
+
url=path,
|
|
144
|
+
params=params,
|
|
145
|
+
json=json_data,
|
|
146
|
+
data=data,
|
|
147
|
+
files=files,
|
|
148
|
+
timeout=self._timeout,
|
|
149
|
+
)
|
|
150
|
+
except httpx.HTTPError as exc: # pragma: no cover - passthrough to caller
|
|
151
|
+
raise TransportError(str(exc)) from exc
|
|
152
|
+
|
|
153
|
+
return self._handle_response(response, unwrap=unwrap)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _handle_response(response: httpx.Response, *, unwrap: bool) -> Any:
|
|
157
|
+
content_type = response.headers.get("content-type", "")
|
|
158
|
+
|
|
159
|
+
parsed: Mapping[str, Any] | None = None
|
|
160
|
+
if "application/json" in content_type:
|
|
161
|
+
try:
|
|
162
|
+
parsed = response.json()
|
|
163
|
+
except ValueError:
|
|
164
|
+
parsed = None
|
|
165
|
+
else:
|
|
166
|
+
parsed = None
|
|
167
|
+
|
|
168
|
+
if response.status_code >= 400:
|
|
169
|
+
message = response.reason_phrase
|
|
170
|
+
payload: Mapping[str, Any] | None = parsed
|
|
171
|
+
code: int | None = None
|
|
172
|
+
error: str | None = None
|
|
173
|
+
if payload and isinstance(payload, Mapping):
|
|
174
|
+
message = str(payload.get("msg") or payload.get("message") or message)
|
|
175
|
+
error = payload.get("error")
|
|
176
|
+
try:
|
|
177
|
+
code_val = payload.get("code")
|
|
178
|
+
if isinstance(code_val, int):
|
|
179
|
+
code = code_val
|
|
180
|
+
except Exception: # pragma: no cover - defensive
|
|
181
|
+
code = None
|
|
182
|
+
raise APIError(
|
|
183
|
+
status_code=response.status_code,
|
|
184
|
+
code=code,
|
|
185
|
+
message=message,
|
|
186
|
+
error=error,
|
|
187
|
+
payload=payload,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if parsed is None:
|
|
191
|
+
if unwrap:
|
|
192
|
+
return response.text
|
|
193
|
+
return {"code": response.status_code, "data": response.text, "msg": response.reason_phrase}
|
|
194
|
+
|
|
195
|
+
app_code = parsed.get("code")
|
|
196
|
+
if isinstance(app_code, int) and app_code >= 400:
|
|
197
|
+
raise APIError(
|
|
198
|
+
status_code=response.status_code,
|
|
199
|
+
code=app_code,
|
|
200
|
+
message=str(parsed.get("msg") or response.reason_phrase),
|
|
201
|
+
error=parsed.get("error"),
|
|
202
|
+
payload=parsed,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return parsed.get("data") if unwrap else parsed
|
|
206
|
+
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
High-level synchronous client for the Acontext API.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Mapping
|
|
6
7
|
from typing import Any, BinaryIO
|
|
7
8
|
|
|
8
9
|
import httpx
|
|
@@ -16,41 +17,69 @@ from .resources.blocks import BlocksAPI as BlocksAPI
|
|
|
16
17
|
from .resources.sessions import SessionsAPI as SessionsAPI
|
|
17
18
|
from .resources.spaces import SpacesAPI as SpacesAPI
|
|
18
19
|
|
|
19
|
-
class AcontextClient:
|
|
20
|
-
"""
|
|
21
|
-
Synchronous HTTP client for the Acontext REST API.
|
|
22
|
-
|
|
23
|
-
Example::
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
with AcontextClient(api_key="sk_...") as client:
|
|
28
|
-
spaces = client.spaces.list()
|
|
29
|
-
session = client.sessions.create(space_id=spaces[0]["id"])
|
|
30
|
-
client.sessions.send_message(
|
|
31
|
-
session["id"],
|
|
32
|
-
role="user",
|
|
33
|
-
parts=[MessagePart.text_part("Hello Acontext!")],
|
|
34
|
-
)
|
|
35
|
-
"""
|
|
21
|
+
class AcontextClient:
|
|
36
22
|
|
|
37
23
|
def __init__(
|
|
38
24
|
self,
|
|
39
25
|
*,
|
|
40
|
-
api_key: str,
|
|
41
|
-
base_url: str =
|
|
26
|
+
api_key: str | None = None,
|
|
27
|
+
base_url: str | None = None,
|
|
42
28
|
timeout: float | httpx.Timeout | None = 10.0,
|
|
43
29
|
user_agent: str | None = None,
|
|
44
30
|
client: httpx.Client | None = None,
|
|
45
31
|
) -> None:
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
"""
|
|
33
|
+
Initialize the Acontext client.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
api_key: API key for authentication. Can also be set via ACONTEXT_API_KEY env var.
|
|
37
|
+
base_url: Base URL for the API. Defaults to DEFAULT_BASE_URL. Can also be set via ACONTEXT_BASE_URL env var.
|
|
38
|
+
timeout: Request timeout in seconds. Defaults to 10.0. Can also be set via ACONTEXT_TIMEOUT env var.
|
|
39
|
+
Can also be an httpx.Timeout object.
|
|
40
|
+
user_agent: Custom user agent string. Can also be set via ACONTEXT_USER_AGENT env var.
|
|
41
|
+
client: Optional httpx.Client instance to reuse. If provided, headers and base_url
|
|
42
|
+
will be merged with the client configuration.
|
|
43
|
+
"""
|
|
44
|
+
# Priority: explicit parameters > environment variables > defaults
|
|
45
|
+
# Load api_key from parameter or environment variable
|
|
46
|
+
api_key = api_key or os.getenv("ACONTEXT_API_KEY")
|
|
47
|
+
if not api_key or not api_key.strip():
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"api_key is required. Provide it either as a parameter (api_key='...') "
|
|
50
|
+
"or set the ACONTEXT_API_KEY environment variable."
|
|
51
|
+
)
|
|
48
52
|
|
|
53
|
+
# Load other parameters from environment variables if not provided
|
|
54
|
+
if base_url is None:
|
|
55
|
+
base_url = os.getenv("ACONTEXT_BASE_URL", DEFAULT_BASE_URL)
|
|
49
56
|
base_url = base_url.rstrip("/")
|
|
57
|
+
|
|
58
|
+
if user_agent is None:
|
|
59
|
+
user_agent = os.getenv("ACONTEXT_USER_AGENT", DEFAULT_USER_AGENT)
|
|
60
|
+
|
|
61
|
+
# Handle timeout: support both float and httpx.Timeout
|
|
62
|
+
if timeout is None:
|
|
63
|
+
timeout_str = os.getenv("ACONTEXT_TIMEOUT")
|
|
64
|
+
if timeout_str:
|
|
65
|
+
try:
|
|
66
|
+
timeout = float(timeout_str)
|
|
67
|
+
except ValueError:
|
|
68
|
+
timeout = 10.0
|
|
69
|
+
else:
|
|
70
|
+
timeout = 10.0
|
|
71
|
+
|
|
72
|
+
# Determine actual timeout value
|
|
73
|
+
actual_timeout: float | httpx.Timeout
|
|
74
|
+
if isinstance(timeout, httpx.Timeout):
|
|
75
|
+
actual_timeout = timeout
|
|
76
|
+
else:
|
|
77
|
+
actual_timeout = float(timeout)
|
|
78
|
+
|
|
50
79
|
headers = {
|
|
51
80
|
"Authorization": f"Bearer {api_key}",
|
|
52
81
|
"Accept": "application/json",
|
|
53
|
-
"User-Agent": user_agent
|
|
82
|
+
"User-Agent": user_agent,
|
|
54
83
|
}
|
|
55
84
|
|
|
56
85
|
if client is not None:
|
|
@@ -63,11 +92,15 @@ class AcontextClient:
|
|
|
63
92
|
client.headers[name] = value
|
|
64
93
|
self._base_url = str(client.base_url) or base_url
|
|
65
94
|
else:
|
|
66
|
-
self._client = httpx.Client(
|
|
95
|
+
self._client = httpx.Client(
|
|
96
|
+
base_url=base_url,
|
|
97
|
+
headers=headers,
|
|
98
|
+
timeout=actual_timeout,
|
|
99
|
+
)
|
|
67
100
|
self._owns_client = True
|
|
68
101
|
self._base_url = base_url
|
|
69
102
|
|
|
70
|
-
self._timeout =
|
|
103
|
+
self._timeout = actual_timeout
|
|
71
104
|
|
|
72
105
|
self.spaces = SpacesAPI(self)
|
|
73
106
|
self.sessions = SessionsAPI(self)
|
|
@@ -98,8 +131,8 @@ class AcontextClient:
|
|
|
98
131
|
path: str,
|
|
99
132
|
*,
|
|
100
133
|
params: Mapping[str, Any] | None = None,
|
|
101
|
-
json_data: Mapping[str, Any] |
|
|
102
|
-
data: Mapping[str, Any] |
|
|
134
|
+
json_data: Mapping[str, Any] | None = None,
|
|
135
|
+
data: Mapping[str, Any] | None = None,
|
|
103
136
|
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
104
137
|
unwrap: bool = True,
|
|
105
138
|
) -> Any:
|
|
@@ -122,10 +155,10 @@ class AcontextClient:
|
|
|
122
155
|
def _handle_response(response: httpx.Response, *, unwrap: bool) -> Any:
|
|
123
156
|
content_type = response.headers.get("content-type", "")
|
|
124
157
|
|
|
125
|
-
parsed: Mapping[str, Any] |
|
|
126
|
-
if "application/json" in content_type
|
|
158
|
+
parsed: Mapping[str, Any] | None
|
|
159
|
+
if "application/json" in content_type:
|
|
127
160
|
try:
|
|
128
|
-
parsed = response.json()
|
|
161
|
+
parsed = response.json() # dict
|
|
129
162
|
except ValueError:
|
|
130
163
|
parsed = None
|
|
131
164
|
else:
|
|
@@ -133,7 +166,7 @@ class AcontextClient:
|
|
|
133
166
|
|
|
134
167
|
if response.status_code >= 400:
|
|
135
168
|
message = response.reason_phrase
|
|
136
|
-
payload: Mapping[str, Any] |
|
|
169
|
+
payload: Mapping[str, Any] | None = parsed
|
|
137
170
|
code: int | None = None
|
|
138
171
|
error: str | None = None
|
|
139
172
|
if payload and isinstance(payload, Mapping):
|
|
@@ -158,11 +191,6 @@ class AcontextClient:
|
|
|
158
191
|
return response.text
|
|
159
192
|
return {"code": response.status_code, "data": response.text, "msg": response.reason_phrase}
|
|
160
193
|
|
|
161
|
-
if not isinstance(parsed, Mapping):
|
|
162
|
-
if unwrap:
|
|
163
|
-
return parsed
|
|
164
|
-
return parsed
|
|
165
|
-
|
|
166
194
|
app_code = parsed.get("code")
|
|
167
195
|
if isinstance(app_code, int) and app_code >= 400:
|
|
168
196
|
raise APIError(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common typing helpers used by resource modules to avoid circular imports.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Mapping
|
|
6
|
+
from typing import Any, BinaryIO, Protocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RequesterProtocol(Protocol):
|
|
10
|
+
def request(
|
|
11
|
+
self,
|
|
12
|
+
method: str,
|
|
13
|
+
path: str,
|
|
14
|
+
*,
|
|
15
|
+
params: Mapping[str, Any] | None = None,
|
|
16
|
+
json_data: Mapping[str, Any] | None = None,
|
|
17
|
+
data: Mapping[str, Any] | None = None,
|
|
18
|
+
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
19
|
+
unwrap: bool = True,
|
|
20
|
+
) -> Any:
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncRequesterProtocol(Protocol):
|
|
25
|
+
def request(
|
|
26
|
+
self,
|
|
27
|
+
method: str,
|
|
28
|
+
path: str,
|
|
29
|
+
*,
|
|
30
|
+
params: Mapping[str, Any] | None = None,
|
|
31
|
+
json_data: Mapping[str, Any] | None = None,
|
|
32
|
+
data: Mapping[str, Any] | None = None,
|
|
33
|
+
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
34
|
+
unwrap: bool = True,
|
|
35
|
+
) -> Awaitable[Any]:
|
|
36
|
+
...
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Custom exceptions raised by the acontext Python client.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from collections.abc import Mapping
|
|
5
|
+
from collections.abc import Mapping
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ class APIError(AcontextError):
|
|
|
29
29
|
code: int | None = None,
|
|
30
30
|
message: str | None = None,
|
|
31
31
|
error: str | None = None,
|
|
32
|
-
payload: Mapping[str, Any] |
|
|
32
|
+
payload: Mapping[str, Any] | None = None,
|
|
33
33
|
) -> None:
|
|
34
34
|
self.status_code = status_code
|
|
35
35
|
self.code = code
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Support for constructing session messages.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from collections.abc import Mapping,
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import Any, Literal
|
|
8
8
|
|
|
@@ -25,14 +25,6 @@ class MessagePart:
|
|
|
25
25
|
meta: Mapping[str, Any] | None = None
|
|
26
26
|
file_field: str | None = None
|
|
27
27
|
|
|
28
|
-
@classmethod
|
|
29
|
-
def text_part(cls, text: str, *, meta: Mapping[str, Any] | None = None) -> "MessagePart":
|
|
30
|
-
return cls(type="text", text=text, meta=meta)
|
|
31
|
-
|
|
32
|
-
@classmethod
|
|
33
|
-
def file_field_part(cls, file_field: str, *, meta: Mapping[str, Any] | None = None) -> "MessagePart":
|
|
34
|
-
return cls(type="file", file_field=file_field, meta=meta)
|
|
35
|
-
|
|
36
28
|
@dataclass(slots=True)
|
|
37
29
|
class AcontextMessage:
|
|
38
30
|
"""
|
|
@@ -41,7 +33,7 @@ class AcontextMessage:
|
|
|
41
33
|
|
|
42
34
|
role: Literal["user", "assistant", "system"]
|
|
43
35
|
parts: list[MessagePart]
|
|
44
|
-
meta:
|
|
36
|
+
meta: Mapping[str, Any] | None = None
|
|
45
37
|
|
|
46
38
|
|
|
47
39
|
def build_acontext_message(
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"""Resource-specific API helpers for the Acontext client."""
|
|
2
2
|
|
|
3
|
+
from .async_blocks import AsyncBlocksAPI
|
|
4
|
+
from .async_disks import AsyncDisksAPI, AsyncDiskArtifactsAPI
|
|
5
|
+
from .async_sessions import AsyncSessionsAPI
|
|
6
|
+
from .async_spaces import AsyncSpacesAPI
|
|
3
7
|
from .blocks import BlocksAPI
|
|
4
8
|
from .disks import DisksAPI, DiskArtifactsAPI
|
|
5
9
|
from .sessions import SessionsAPI
|
|
@@ -11,4 +15,9 @@ __all__ = [
|
|
|
11
15
|
"BlocksAPI",
|
|
12
16
|
"SessionsAPI",
|
|
13
17
|
"SpacesAPI",
|
|
18
|
+
"AsyncDisksAPI",
|
|
19
|
+
"AsyncDiskArtifactsAPI",
|
|
20
|
+
"AsyncBlocksAPI",
|
|
21
|
+
"AsyncSessionsAPI",
|
|
22
|
+
"AsyncSpacesAPI",
|
|
14
23
|
]
|