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.
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: acontext
3
- Version: 0.0.1.dev1
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.dev1"
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 = ["httpx>=0.28.1"]
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
  High-level synchronous client for the Acontext API.
3
3
  """
4
4
 
5
- from typing import Any, BinaryIO, Mapping, MutableMapping
5
+ from collections.abc import Mapping, MutableMapping
6
+ from typing import Any, BinaryIO
6
7
 
7
8
  import httpx
8
9
 
@@ -2,7 +2,8 @@
2
2
  Common typing helpers used by resource modules to avoid circular imports.
3
3
  """
4
4
 
5
- from typing import Any, BinaryIO, Mapping, MutableMapping, Protocol
5
+ from collections.abc import Mapping, MutableMapping
6
+ from typing import Any, BinaryIO, Protocol
6
7
 
7
8
 
8
9
  class RequesterProtocol(Protocol):
@@ -2,7 +2,8 @@
2
2
  Custom exceptions raised by the acontext Python client.
3
3
  """
4
4
 
5
- from typing import Any, Mapping, MutableMapping
5
+ from collections.abc import Mapping, MutableMapping
6
+ from typing import Any
6
7
 
7
8
 
8
9
  class AcontextError(Exception):
@@ -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")
@@ -2,7 +2,8 @@
2
2
  Block endpoints.
3
3
  """
4
4
 
5
- from typing import Any, Mapping, MutableMapping
5
+ from collections.abc import Mapping, MutableMapping
6
+ from typing import Any
6
7
 
7
8
  from ..client_types import RequesterProtocol
8
9
 
@@ -3,7 +3,8 @@ Disk and artifact endpoints.
3
3
  """
4
4
 
5
5
  import json
6
- from typing import Any, BinaryIO, Mapping, MutableMapping, cast
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 typing import Any, Mapping, MutableMapping, Sequence
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
- role: str,
67
- parts: Sequence[MessagePart | str | Mapping[str, Any]],
68
- format: str | None = None,
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 role not in SUPPORTED_ROLES:
71
- raise ValueError(f"role must be one of {SUPPORTED_ROLES!r}")
72
- if not parts:
73
- raise ValueError("parts must contain at least one entry")
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
- if files:
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=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: str | None = None,
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)
@@ -2,7 +2,8 @@
2
2
  Spaces endpoints.
3
3
  """
4
4
 
5
- from typing import Any, Mapping, MutableMapping
5
+ from collections.abc import Mapping, MutableMapping
6
+ from typing import Any
6
7
 
7
8
  from ..client_types import RequesterProtocol
8
9
 
@@ -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, Tuple
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) -> Tuple[str, BinaryIO, str | None]:
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