acontext 0.0.1.dev1__tar.gz → 0.0.1.dev2__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.dev1 → acontext-0.0.1.dev2}/PKG-INFO +3 -1
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/pyproject.toml +6 -2
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/__init__.py +2 -0
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/client.py +2 -1
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/client_types.py +2 -1
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/errors.py +2 -1
- acontext-0.0.1.dev2/src/acontext/messages.py +83 -0
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/resources/blocks.py +2 -1
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/resources/disks.py +2 -1
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/resources/sessions.py +56 -21
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/resources/spaces.py +2 -1
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/uploads.py +2 -2
- acontext-0.0.1.dev1/src/acontext/messages.py +0 -94
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/README.md +0 -0
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/_constants.py +0 -0
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/py.typed +0 -0
- {acontext-0.0.1.dev1 → acontext-0.0.1.dev2}/src/acontext/resources/__init__.py +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: acontext
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev2
|
|
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
|
+
Requires-Dist: openai>=2.6.1
|
|
8
|
+
Requires-Dist: anthropic>=0.72.0
|
|
7
9
|
Requires-Python: >=3.10
|
|
8
10
|
Project-URL: Homepage, https://github.com/memodb-io/Acontext
|
|
9
11
|
Project-URL: Issues, https://github.com/memodb-io/Acontext/issues
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acontext"
|
|
3
|
-
version = "0.0.1.
|
|
3
|
+
version = "0.0.1.dev2"
|
|
4
4
|
description = "Python SDK for the Acontext API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
|
-
dependencies = [
|
|
7
|
+
dependencies = [
|
|
8
|
+
"httpx>=0.28.1",
|
|
9
|
+
"openai>=2.6.1",
|
|
10
|
+
"anthropic>=0.72.0"
|
|
11
|
+
]
|
|
8
12
|
keywords = ["acontext", "sdk", "client", "api"]
|
|
9
13
|
|
|
10
14
|
[project.urls]
|
|
@@ -5,12 +5,14 @@ Python SDK for the Acontext API.
|
|
|
5
5
|
from importlib import metadata as _metadata
|
|
6
6
|
|
|
7
7
|
from .client import AcontextClient, FileUpload, MessagePart
|
|
8
|
+
from .messages import AcontextMessage
|
|
8
9
|
from .resources import BlocksAPI, DiskArtifactsAPI, DisksAPI, SessionsAPI, SpacesAPI
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
"AcontextClient",
|
|
12
13
|
"FileUpload",
|
|
13
14
|
"MessagePart",
|
|
15
|
+
"AcontextMessage",
|
|
14
16
|
"DisksAPI",
|
|
15
17
|
"DiskArtifactsAPI",
|
|
16
18
|
"BlocksAPI",
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Common typing helpers used by resource modules to avoid circular imports.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Mapping, MutableMapping
|
|
6
|
+
from typing import Any, BinaryIO, Protocol
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class RequesterProtocol(Protocol):
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Support for constructing session messages.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, MutableMapping, Sequence
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
@dataclass(slots=True)
|
|
10
|
+
class MessagePart:
|
|
11
|
+
"""
|
|
12
|
+
Represents a single message part for ``/session/{id}/messages``.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
type: One of ``text``, ``image``, ``audio``, ``video``, ``file``, ``tool-call``,
|
|
16
|
+
``tool-result`` or ``data``.
|
|
17
|
+
text: Optional textual payload for ``text`` parts.
|
|
18
|
+
meta: Optional metadata dictionary accepted by the API.
|
|
19
|
+
file_field: Optional field name to use in the multipart body. When omitted the
|
|
20
|
+
client will auto-generate deterministic field names.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
type: str
|
|
24
|
+
text: str | None = None
|
|
25
|
+
meta: Mapping[str, Any] | None = None
|
|
26
|
+
file_field: str | None = None
|
|
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
|
+
@dataclass(slots=True)
|
|
37
|
+
class AcontextMessage:
|
|
38
|
+
"""
|
|
39
|
+
Represents an Acontext-format message payload.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
role: Literal["user", "assistant", "system"]
|
|
43
|
+
parts: list[MessagePart]
|
|
44
|
+
meta: MutableMapping[str, Any] | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_acontext_message(
|
|
48
|
+
*,
|
|
49
|
+
role: Literal["user", "assistant", "system"],
|
|
50
|
+
parts: Sequence[MessagePart | str | Mapping[str, Any]],
|
|
51
|
+
meta: Mapping[str, Any] | None = None,
|
|
52
|
+
) -> AcontextMessage:
|
|
53
|
+
"""
|
|
54
|
+
Construct an Acontext-format message blob and associated multipart files.
|
|
55
|
+
"""
|
|
56
|
+
if role not in {"user", "assistant", "system"}:
|
|
57
|
+
raise ValueError("role must be one of {'user', 'assistant', 'system'}")
|
|
58
|
+
|
|
59
|
+
normalized_parts = [normalize_message_part(part) for part in parts]
|
|
60
|
+
|
|
61
|
+
message = AcontextMessage(
|
|
62
|
+
role=role,
|
|
63
|
+
parts=normalized_parts,
|
|
64
|
+
meta=dict(meta) if meta is not None else None,
|
|
65
|
+
)
|
|
66
|
+
return message
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def normalize_message_part(part: MessagePart | str | Mapping[str, Any]) -> MessagePart:
|
|
70
|
+
if isinstance(part, MessagePart):
|
|
71
|
+
return part
|
|
72
|
+
if isinstance(part, str):
|
|
73
|
+
return MessagePart(type="text", text=part)
|
|
74
|
+
if isinstance(part, Mapping):
|
|
75
|
+
if "type" not in part:
|
|
76
|
+
raise ValueError("mapping message parts must include a 'type'")
|
|
77
|
+
return MessagePart(
|
|
78
|
+
type=str(part["type"]),
|
|
79
|
+
text=part.get("text"),
|
|
80
|
+
meta=part.get("meta"),
|
|
81
|
+
file_field=part.get("file_field"),
|
|
82
|
+
)
|
|
83
|
+
raise TypeError("unsupported message part type")
|
|
@@ -3,7 +3,8 @@ Disk and artifact endpoints.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Mapping, MutableMapping
|
|
7
|
+
from typing import Any, BinaryIO, cast
|
|
7
8
|
|
|
8
9
|
from ..client_types import RequesterProtocol
|
|
9
10
|
from ..uploads import FileUpload, normalize_file_upload
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Sessions endpoints.
|
|
3
|
-
"""
|
|
1
|
+
"""Sessions endpoints."""
|
|
4
2
|
|
|
5
3
|
import json
|
|
6
|
-
from
|
|
4
|
+
from collections.abc import Mapping, MutableMapping
|
|
5
|
+
from dataclasses import asdict
|
|
6
|
+
from typing import Any, BinaryIO, Literal
|
|
7
7
|
|
|
8
|
-
from .._constants import SUPPORTED_ROLES
|
|
9
|
-
from ..messages import MessagePart, build_message_payload
|
|
10
8
|
from ..client_types import RequesterProtocol
|
|
9
|
+
from ..messages import AcontextMessage
|
|
10
|
+
from ..uploads import FileUpload
|
|
11
|
+
from openai.types.chat import ChatCompletionMessageParam
|
|
12
|
+
from anthropic.types import MessageParam
|
|
13
|
+
|
|
14
|
+
UploadPayload = FileUpload | tuple[str, BinaryIO | bytes] | tuple[str, BinaryIO | bytes, str | None]
|
|
15
|
+
MessageBlob = AcontextMessage | ChatCompletionMessageParam | MessageParam
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
class SessionsAPI:
|
|
@@ -59,33 +64,60 @@ class SessionsAPI:
|
|
|
59
64
|
payload = {"space_id": space_id}
|
|
60
65
|
self._requester.request("POST", f"/session/{session_id}/connect_to_space", json_data=payload)
|
|
61
66
|
|
|
67
|
+
def get_tasks(
|
|
68
|
+
self,
|
|
69
|
+
session_id: str,
|
|
70
|
+
*,
|
|
71
|
+
limit: int | None = None,
|
|
72
|
+
cursor: str | None = None,
|
|
73
|
+
) -> Any:
|
|
74
|
+
params: dict[str, Any] = {}
|
|
75
|
+
if limit is not None:
|
|
76
|
+
params["limit"] = limit
|
|
77
|
+
if cursor is not None:
|
|
78
|
+
params["cursor"] = cursor
|
|
79
|
+
return self._requester.request(
|
|
80
|
+
"GET",
|
|
81
|
+
f"/session/{session_id}/task",
|
|
82
|
+
params=params or None,
|
|
83
|
+
)
|
|
84
|
+
|
|
62
85
|
def send_message(
|
|
63
86
|
self,
|
|
64
87
|
session_id: str,
|
|
65
88
|
*,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
blob: MessageBlob,
|
|
90
|
+
format: Literal["acontext", "openai", "anthropic"] = "acontext",
|
|
91
|
+
file_field: str | None = "",
|
|
92
|
+
file: FileUpload | None = None
|
|
69
93
|
) -> Any:
|
|
70
|
-
if
|
|
71
|
-
raise ValueError(
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
if format not in {"acontext", "openai", "anthropic"}:
|
|
95
|
+
raise ValueError("format must be one of {'acontext', 'openai', 'anthropic'}")
|
|
96
|
+
|
|
97
|
+
payload = {
|
|
98
|
+
"format": format,
|
|
99
|
+
}
|
|
100
|
+
if format == "acontext":
|
|
101
|
+
payload["blob"] = asdict(blob)
|
|
102
|
+
else:
|
|
103
|
+
payload["blob"] = blob
|
|
74
104
|
|
|
75
|
-
payload_parts, files = build_message_payload(parts)
|
|
76
|
-
payload = {"role": role, "parts": payload_parts}
|
|
77
|
-
if format is not None:
|
|
78
|
-
payload["format"] = format
|
|
79
105
|
|
|
80
|
-
|
|
106
|
+
file_payload: dict[str, tuple[str, BinaryIO, str | None]] | None = None
|
|
107
|
+
if file:
|
|
108
|
+
# only support upload one file now
|
|
109
|
+
file_payload = {
|
|
110
|
+
file_field: file.as_httpx()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if file_payload:
|
|
81
114
|
form_data = {"payload": json.dumps(payload)}
|
|
82
115
|
return self._requester.request(
|
|
83
116
|
"POST",
|
|
84
117
|
f"/session/{session_id}/messages",
|
|
85
118
|
data=form_data,
|
|
86
|
-
files=
|
|
119
|
+
files=file_payload,
|
|
87
120
|
)
|
|
88
|
-
|
|
89
121
|
return self._requester.request(
|
|
90
122
|
"POST",
|
|
91
123
|
f"/session/{session_id}/messages",
|
|
@@ -99,7 +131,8 @@ class SessionsAPI:
|
|
|
99
131
|
limit: int | None = None,
|
|
100
132
|
cursor: str | None = None,
|
|
101
133
|
with_asset_public_url: bool | None = None,
|
|
102
|
-
format:
|
|
134
|
+
format: Literal["acontext", "openai", "anthropic"] = "acontext",
|
|
135
|
+
time_desc: bool | None = None,
|
|
103
136
|
) -> Any:
|
|
104
137
|
params: dict[str, Any] = {}
|
|
105
138
|
if limit is not None:
|
|
@@ -110,4 +143,6 @@ class SessionsAPI:
|
|
|
110
143
|
params["with_asset_public_url"] = "true" if with_asset_public_url else "false"
|
|
111
144
|
if format is not None:
|
|
112
145
|
params["format"] = format
|
|
146
|
+
if time_desc is not None:
|
|
147
|
+
params["time_desc"] = "true" if time_desc else "false"
|
|
113
148
|
return self._requester.request("GET", f"/session/{session_id}/messages", params=params or None)
|
|
@@ -4,7 +4,7 @@ Utilities for working with file uploads.
|
|
|
4
4
|
|
|
5
5
|
import io
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from typing import BinaryIO
|
|
7
|
+
from typing import BinaryIO
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@dataclass(slots=True)
|
|
@@ -19,7 +19,7 @@ class FileUpload:
|
|
|
19
19
|
content: BinaryIO | bytes
|
|
20
20
|
content_type: str | None = None
|
|
21
21
|
|
|
22
|
-
def as_httpx(self) ->
|
|
22
|
+
def as_httpx(self) -> tuple[str, BinaryIO, str | None]:
|
|
23
23
|
"""
|
|
24
24
|
Convert to the tuple format expected by ``httpx``.
|
|
25
25
|
"""
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Support for constructing session messages.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from typing import Any, BinaryIO, Mapping, MutableMapping, Sequence, Tuple
|
|
7
|
-
|
|
8
|
-
from .uploads import FileUpload, normalize_file_upload
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass(slots=True)
|
|
12
|
-
class MessagePart:
|
|
13
|
-
"""
|
|
14
|
-
Represents a single message part for ``/session/{id}/messages``.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
type: One of ``text``, ``image``, ``audio``, ``video``, ``file``, ``tool-call``,
|
|
18
|
-
``tool-result`` or ``data``.
|
|
19
|
-
text: Optional textual payload for ``text`` parts.
|
|
20
|
-
meta: Optional metadata dictionary accepted by the API.
|
|
21
|
-
file: Optional file attachment; required for binary part types.
|
|
22
|
-
file_field: Optional field name to use in the multipart body. When omitted the
|
|
23
|
-
client will auto-generate deterministic field names.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
type: str
|
|
27
|
-
text: str | None = None
|
|
28
|
-
meta: Mapping[str, Any] | None = None
|
|
29
|
-
file: FileUpload | tuple[str, BinaryIO | bytes] | tuple[str, BinaryIO | bytes, str | None] | None = None
|
|
30
|
-
file_field: str | None = None
|
|
31
|
-
|
|
32
|
-
@classmethod
|
|
33
|
-
def text_part(cls, text: str, *, meta: Mapping[str, Any] | None = None) -> "MessagePart":
|
|
34
|
-
return cls(type="text", text=text, meta=meta)
|
|
35
|
-
|
|
36
|
-
@classmethod
|
|
37
|
-
def file_part(
|
|
38
|
-
cls,
|
|
39
|
-
upload: FileUpload | tuple[str, BinaryIO | bytes] | tuple[str, BinaryIO | bytes, str | None],
|
|
40
|
-
*,
|
|
41
|
-
meta: Mapping[str, Any] | None = None,
|
|
42
|
-
type: str = "file",
|
|
43
|
-
) -> "MessagePart":
|
|
44
|
-
return cls(type=type, file=upload, meta=meta)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def normalize_message_part(part: MessagePart | str | Mapping[str, Any]) -> MessagePart:
|
|
48
|
-
if isinstance(part, MessagePart):
|
|
49
|
-
return part
|
|
50
|
-
if isinstance(part, str):
|
|
51
|
-
return MessagePart(type="text", text=part)
|
|
52
|
-
if isinstance(part, Mapping):
|
|
53
|
-
if "type" not in part:
|
|
54
|
-
raise ValueError("mapping message parts must include a 'type'")
|
|
55
|
-
file = part.get("file")
|
|
56
|
-
normalized_file: FileUpload | tuple[str, BinaryIO | bytes] | tuple[str, BinaryIO | bytes, str | None] | None
|
|
57
|
-
if file is None:
|
|
58
|
-
normalized_file = None
|
|
59
|
-
else:
|
|
60
|
-
normalized_file = file # type: ignore[assignment]
|
|
61
|
-
return MessagePart(
|
|
62
|
-
type=str(part["type"]),
|
|
63
|
-
text=part.get("text"),
|
|
64
|
-
meta=part.get("meta"),
|
|
65
|
-
file=normalized_file,
|
|
66
|
-
file_field=part.get("file_field"),
|
|
67
|
-
)
|
|
68
|
-
raise TypeError("unsupported message part type")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def build_message_payload(
|
|
72
|
-
parts: Sequence[MessagePart | str | Mapping[str, Any]],
|
|
73
|
-
) -> tuple[list[MutableMapping[str, Any]], dict[str, Tuple[str, BinaryIO, str | None]]]:
|
|
74
|
-
payload_parts: list[MutableMapping[str, Any]] = []
|
|
75
|
-
files: dict[str, Tuple[str, BinaryIO, str | None]] = {}
|
|
76
|
-
|
|
77
|
-
for idx, raw_part in enumerate(parts):
|
|
78
|
-
part = normalize_message_part(raw_part)
|
|
79
|
-
payload: MutableMapping[str, Any] = {"type": part.type}
|
|
80
|
-
|
|
81
|
-
if part.meta is not None:
|
|
82
|
-
payload["meta"] = dict(part.meta)
|
|
83
|
-
if part.text is not None:
|
|
84
|
-
payload["text"] = part.text
|
|
85
|
-
|
|
86
|
-
if part.file is not None:
|
|
87
|
-
upload = normalize_file_upload(part.file)
|
|
88
|
-
field_name = part.file_field or f"file_{idx}"
|
|
89
|
-
payload["file_field"] = field_name
|
|
90
|
-
files[field_name] = upload.as_httpx()
|
|
91
|
-
|
|
92
|
-
payload_parts.append(payload)
|
|
93
|
-
|
|
94
|
-
return payload_parts, files
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|