acontext 0.0.1.dev0__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,10 +1,12 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: acontext
3
- Version: 0.0.1.dev0
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-Python: >=3.13
7
+ Requires-Dist: openai>=2.6.1
8
+ Requires-Dist: anthropic>=0.72.0
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
10
12
  Project-URL: Repository, https://github.com/memodb-io/Acontext
@@ -20,7 +22,7 @@ Python SDK for interacting with the Acontext REST API.
20
22
  pip install acontext
21
23
  ```
22
24
 
23
- > Requires Python 3.13 or newer.
25
+ > Requires Python 3.10 or newer.
24
26
 
25
27
  ### Quickstart
26
28
 
@@ -42,9 +44,33 @@ with AcontextClient(api_key="sk_project_token") as client:
42
44
  )
43
45
  ```
44
46
 
45
- See the inline docstrings for the full list of helpers covering sessions, spaces, artifacts and file uploads.
47
+ See the inline docstrings for the full list of helpers covering sessions, spaces, disks, and artifact uploads.
46
48
 
47
- ### Working with pages and blocks
49
+ ### Managing disks and artifacts
50
+
51
+ Artifacts now live under project disks. Create a disk first, then upload files through the disk-scoped helper:
52
+
53
+ ```python
54
+ from acontext import AcontextClient, FileUpload
55
+
56
+ client = AcontextClient(api_key="sk_project_token")
57
+ try:
58
+ disk = client.disks.create()
59
+ client.disks.artifacts.upsert(
60
+ disk["id"],
61
+ file=FileUpload(
62
+ filename="retro_notes.md",
63
+ content=b"# Retro Notes\nWe shipped file uploads successfully!\n",
64
+ content_type="text/markdown",
65
+ ),
66
+ file_path="notes/retro.md",
67
+ meta={"source": "readme-demo"},
68
+ )
69
+ finally:
70
+ client.close()
71
+ ```
72
+
73
+ ### Working with blocks
48
74
 
49
75
  ```python
50
76
  from acontext import AcontextClient
@@ -53,7 +79,7 @@ client = AcontextClient(api_key="sk_project_token")
53
79
 
54
80
  space = client.spaces.create()
55
81
  try:
56
- page = client.pages.create(space["id"], title="Kick-off Notes")
82
+ page = client.blocks.create(space["id"], block_type="page", title="Kick-off Notes")
57
83
  client.blocks.create(
58
84
  space["id"],
59
85
  parent_id=page["id"],
@@ -8,7 +8,7 @@ Python SDK for interacting with the Acontext REST API.
8
8
  pip install acontext
9
9
  ```
10
10
 
11
- > Requires Python 3.13 or newer.
11
+ > Requires Python 3.10 or newer.
12
12
 
13
13
  ### Quickstart
14
14
 
@@ -30,9 +30,33 @@ with AcontextClient(api_key="sk_project_token") as client:
30
30
  )
31
31
  ```
32
32
 
33
- See the inline docstrings for the full list of helpers covering sessions, spaces, artifacts and file uploads.
33
+ See the inline docstrings for the full list of helpers covering sessions, spaces, disks, and artifact uploads.
34
34
 
35
- ### Working with pages and blocks
35
+ ### Managing disks and artifacts
36
+
37
+ Artifacts now live under project disks. Create a disk first, then upload files through the disk-scoped helper:
38
+
39
+ ```python
40
+ from acontext import AcontextClient, FileUpload
41
+
42
+ client = AcontextClient(api_key="sk_project_token")
43
+ try:
44
+ disk = client.disks.create()
45
+ client.disks.artifacts.upsert(
46
+ disk["id"],
47
+ file=FileUpload(
48
+ filename="retro_notes.md",
49
+ content=b"# Retro Notes\nWe shipped file uploads successfully!\n",
50
+ content_type="text/markdown",
51
+ ),
52
+ file_path="notes/retro.md",
53
+ meta={"source": "readme-demo"},
54
+ )
55
+ finally:
56
+ client.close()
57
+ ```
58
+
59
+ ### Working with blocks
36
60
 
37
61
  ```python
38
62
  from acontext import AcontextClient
@@ -41,7 +65,7 @@ client = AcontextClient(api_key="sk_project_token")
41
65
 
42
66
  space = client.spaces.create()
43
67
  try:
44
- page = client.pages.create(space["id"], title="Kick-off Notes")
68
+ page = client.blocks.create(space["id"], block_type="page", title="Kick-off Notes")
45
69
  client.blocks.create(
46
70
  space["id"],
47
71
  parent_id=page["id"],
@@ -1,10 +1,14 @@
1
1
  [project]
2
2
  name = "acontext"
3
- version = "0.0.1.dev0"
3
+ version = "0.0.1.dev2"
4
4
  description = "Python SDK for the Acontext API"
5
5
  readme = "README.md"
6
- requires-python = ">=3.13"
7
- dependencies = ["httpx>=0.28.1"]
6
+ requires-python = ">=3.10"
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,23 +5,17 @@ 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 .resources import (
9
- ArtifactFilesAPI,
10
- ArtifactsAPI,
11
- BlocksAPI,
12
- PagesAPI,
13
- SessionsAPI,
14
- SpacesAPI,
15
- )
8
+ from .messages import AcontextMessage
9
+ from .resources import BlocksAPI, DiskArtifactsAPI, DisksAPI, SessionsAPI, SpacesAPI
16
10
 
17
11
  __all__ = [
18
12
  "AcontextClient",
19
13
  "FileUpload",
20
14
  "MessagePart",
21
- "ArtifactsAPI",
22
- "ArtifactFilesAPI",
15
+ "AcontextMessage",
16
+ "DisksAPI",
17
+ "DiskArtifactsAPI",
23
18
  "BlocksAPI",
24
- "PagesAPI",
25
19
  "SessionsAPI",
26
20
  "SpacesAPI",
27
21
  "__version__",
@@ -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
 
@@ -10,9 +11,8 @@ from ._constants import DEFAULT_BASE_URL, DEFAULT_USER_AGENT
10
11
  from .errors import APIError, TransportError
11
12
  from .messages import MessagePart as MessagePart
12
13
  from .uploads import FileUpload as FileUpload
13
- from .resources.artifacts import ArtifactsAPI as ArtifactsAPI
14
+ from .resources.disks import DisksAPI as DisksAPI
14
15
  from .resources.blocks import BlocksAPI as BlocksAPI
15
- from .resources.pages import PagesAPI as PagesAPI
16
16
  from .resources.sessions import SessionsAPI as SessionsAPI
17
17
  from .resources.spaces import SpacesAPI as SpacesAPI
18
18
 
@@ -71,8 +71,8 @@ class AcontextClient:
71
71
 
72
72
  self.spaces = SpacesAPI(self)
73
73
  self.sessions = SessionsAPI(self)
74
- self.artifacts = ArtifactsAPI(self)
75
- self.pages = PagesAPI(self)
74
+ self.disks = DisksAPI(self)
75
+ self.artifacts = self.disks.artifacts
76
76
  self.blocks = BlocksAPI(self)
77
77
 
78
78
  @property
@@ -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")
@@ -1,19 +1,14 @@
1
1
  """Resource-specific API helpers for the Acontext client."""
2
2
 
3
- from .artifacts import (
4
- ArtifactFilesAPI,
5
- ArtifactsAPI,
6
- )
7
3
  from .blocks import BlocksAPI
8
- from .pages import PagesAPI
4
+ from .disks import DisksAPI, DiskArtifactsAPI
9
5
  from .sessions import SessionsAPI
10
6
  from .spaces import SpacesAPI
11
7
 
12
8
  __all__ = [
13
- "ArtifactsAPI",
14
- "ArtifactFilesAPI",
9
+ "DisksAPI",
10
+ "DiskArtifactsAPI",
15
11
  "BlocksAPI",
16
- "PagesAPI",
17
12
  "SessionsAPI",
18
13
  "SpacesAPI",
19
14
  ]
@@ -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
 
@@ -11,26 +12,34 @@ class BlocksAPI:
11
12
  def __init__(self, requester: RequesterProtocol) -> None:
12
13
  self._requester = requester
13
14
 
14
- def list(self, space_id: str, *, parent_id: str) -> Any:
15
- if not parent_id:
16
- raise ValueError("parent_id is required")
17
- params = {"parent_id": parent_id}
18
- return self._requester.request("GET", f"/space/{space_id}/block", params=params)
15
+ def list(
16
+ self,
17
+ space_id: str,
18
+ *,
19
+ parent_id: str | None = None,
20
+ block_type: str | None = None,
21
+ ) -> Any:
22
+ params: dict[str, Any] = {}
23
+ if parent_id is not None:
24
+ params["parent_id"] = parent_id
25
+ if block_type is not None:
26
+ params["type"] = block_type
27
+ return self._requester.request("GET", f"/space/{space_id}/block", params=params or None)
19
28
 
20
29
  def create(
21
30
  self,
22
31
  space_id: str,
23
32
  *,
24
- parent_id: str,
25
33
  block_type: str,
34
+ parent_id: str | None = None,
26
35
  title: str | None = None,
27
36
  props: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
28
37
  ) -> Any:
29
- if not parent_id:
30
- raise ValueError("parent_id is required")
31
38
  if not block_type:
32
39
  raise ValueError("block_type is required")
33
- payload: dict[str, Any] = {"parent_id": parent_id, "type": block_type}
40
+ payload: dict[str, Any] = {"type": block_type}
41
+ if parent_id is not None:
42
+ payload["parent_id"] = parent_id
34
43
  if title is not None:
35
44
  payload["title"] = title
36
45
  if props is not None:
@@ -65,14 +74,16 @@ class BlocksAPI:
65
74
  space_id: str,
66
75
  block_id: str,
67
76
  *,
68
- parent_id: str,
77
+ parent_id: str | None = None,
69
78
  sort: int | None = None,
70
79
  ) -> None:
71
- if not parent_id:
72
- raise ValueError("parent_id is required")
73
- payload: dict[str, Any] = {"parent_id": parent_id}
80
+ payload: dict[str, Any] = {}
81
+ if parent_id is not None:
82
+ payload["parent_id"] = parent_id
74
83
  if sort is not None:
75
84
  payload["sort"] = sort
85
+ if not payload:
86
+ raise ValueError("parent_id or sort must be provided")
76
87
  self._requester.request("PUT", f"/space/{space_id}/block/{block_id}/move", json_data=payload)
77
88
 
78
89
  def update_sort(self, space_id: str, block_id: str, *, sort: int) -> None:
@@ -0,0 +1,109 @@
1
+ """
2
+ Disk and artifact endpoints.
3
+ """
4
+
5
+ import json
6
+ from collections.abc import Mapping, MutableMapping
7
+ from typing import Any, BinaryIO, cast
8
+
9
+ from ..client_types import RequesterProtocol
10
+ from ..uploads import FileUpload, normalize_file_upload
11
+
12
+
13
+ def _bool_to_str(value: bool) -> str:
14
+ return "true" if value else "false"
15
+
16
+
17
+ class DisksAPI:
18
+ def __init__(self, requester: RequesterProtocol) -> None:
19
+ self._requester = requester
20
+ self.artifacts = DiskArtifactsAPI(requester)
21
+
22
+ def list(self) -> Any:
23
+ return self._requester.request("GET", "/disk")
24
+
25
+ def create(self) -> Any:
26
+ return self._requester.request("POST", "/disk")
27
+
28
+ def delete(self, disk_id: str) -> None:
29
+ self._requester.request("DELETE", f"/disk/{disk_id}")
30
+
31
+
32
+ class DiskArtifactsAPI:
33
+ def __init__(self, requester: RequesterProtocol) -> None:
34
+ self._requester = requester
35
+
36
+ def upsert(
37
+ self,
38
+ disk_id: str,
39
+ *,
40
+ file: FileUpload
41
+ | tuple[str, BinaryIO | bytes]
42
+ | tuple[str, BinaryIO | bytes, str | None],
43
+ file_path: str | None = None,
44
+ meta: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
45
+ ) -> Any:
46
+ upload = normalize_file_upload(file)
47
+ files = {"file": upload.as_httpx()}
48
+ form: dict[str, Any] = {}
49
+ if file_path:
50
+ form["file_path"] = file_path
51
+ if meta is not None:
52
+ form["meta"] = json.dumps(cast(Mapping[str, Any], meta))
53
+ return self._requester.request(
54
+ "POST",
55
+ f"/disk/{disk_id}/artifact",
56
+ data=form or None,
57
+ files=files,
58
+ )
59
+
60
+ def get(
61
+ self,
62
+ disk_id: str,
63
+ *,
64
+ file_path: str,
65
+ with_public_url: bool | None = None,
66
+ with_content: bool | None = None,
67
+ expire: int | None = None,
68
+ ) -> Any:
69
+ params: dict[str, Any] = {"file_path": file_path}
70
+ if with_public_url is not None:
71
+ params["with_public_url"] = _bool_to_str(with_public_url)
72
+ if with_content is not None:
73
+ params["with_content"] = _bool_to_str(with_content)
74
+ if expire is not None:
75
+ params["expire"] = expire
76
+ return self._requester.request("GET", f"/disk/{disk_id}/artifact", params=params)
77
+
78
+ def update(
79
+ self,
80
+ disk_id: str,
81
+ *,
82
+ file_path: str,
83
+ meta: Mapping[str, Any] | MutableMapping[str, Any],
84
+ ) -> Any:
85
+ payload = {
86
+ "file_path": file_path,
87
+ "meta": json.dumps(cast(Mapping[str, Any], meta)),
88
+ }
89
+ return self._requester.request("PUT", f"/disk/{disk_id}/artifact", json_data=payload)
90
+
91
+ def delete(
92
+ self,
93
+ disk_id: str,
94
+ *,
95
+ file_path: str,
96
+ ) -> None:
97
+ params = {"file_path": file_path}
98
+ self._requester.request("DELETE", f"/disk/{disk_id}/artifact", params=params)
99
+
100
+ def list(
101
+ self,
102
+ disk_id: str,
103
+ *,
104
+ path: str | None = None,
105
+ ) -> Any:
106
+ params: dict[str, Any] = {}
107
+ if path is not None:
108
+ params["path"] = path
109
+ return self._requester.request("GET", f"/disk/{disk_id}/artifact/ls", params=params or None)
@@ -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,30 +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]],
89
+ blob: MessageBlob,
90
+ format: Literal["acontext", "openai", "anthropic"] = "acontext",
91
+ file_field: str | None = "",
92
+ file: FileUpload | None = None
68
93
  ) -> Any:
69
- if role not in SUPPORTED_ROLES:
70
- raise ValueError(f"role must be one of {SUPPORTED_ROLES!r}")
71
- if not parts:
72
- 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
73
104
 
74
- payload_parts, files = build_message_payload(parts)
75
- payload = {"role": role, "parts": payload_parts}
76
105
 
77
- 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:
78
114
  form_data = {"payload": json.dumps(payload)}
79
115
  return self._requester.request(
80
116
  "POST",
81
117
  f"/session/{session_id}/messages",
82
118
  data=form_data,
83
- files=files,
119
+ files=file_payload,
84
120
  )
85
-
86
121
  return self._requester.request(
87
122
  "POST",
88
123
  f"/session/{session_id}/messages",
@@ -96,6 +131,8 @@ class SessionsAPI:
96
131
  limit: int | None = None,
97
132
  cursor: str | None = None,
98
133
  with_asset_public_url: bool | None = None,
134
+ format: Literal["acontext", "openai", "anthropic"] = "acontext",
135
+ time_desc: bool | None = None,
99
136
  ) -> Any:
100
137
  params: dict[str, Any] = {}
101
138
  if limit is not None:
@@ -104,4 +141,8 @@ class SessionsAPI:
104
141
  params["cursor"] = cursor
105
142
  if with_asset_public_url is not None:
106
143
  params["with_asset_public_url"] = "true" if with_asset_public_url else "false"
144
+ if format is not None:
145
+ params["format"] = format
146
+ if time_desc is not None:
147
+ params["time_desc"] = "true" if time_desc else "false"
107
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
 
@@ -14,6 +15,9 @@ class SpacesAPI:
14
15
  def list(self) -> Any:
15
16
  return self._requester.request("GET", "/space")
16
17
 
18
+ def status(self) -> Any:
19
+ return self._requester.request("GET", "/space/status")
20
+
17
21
  def create(self, *, configs: Mapping[str, Any] | MutableMapping[str, Any] | None = None) -> Any:
18
22
  payload: dict[str, Any] = {}
19
23
  if configs is not None:
@@ -34,3 +38,21 @@ class SpacesAPI:
34
38
 
35
39
  def get_configs(self, space_id: str) -> Any:
36
40
  return self._requester.request("GET", f"/space/{space_id}/configs")
41
+
42
+ def get_semantic_answer(self, space_id: str, *, query: str) -> Any:
43
+ if not query:
44
+ raise ValueError("query is required")
45
+ params = {"query": query}
46
+ return self._requester.request("GET", f"/space/{space_id}/semantic_answer", params=params)
47
+
48
+ def get_semantic_global(self, space_id: str, *, query: str) -> Any:
49
+ if not query:
50
+ raise ValueError("query is required")
51
+ params = {"query": query}
52
+ return self._requester.request("GET", f"/space/{space_id}/semantic_global", params=params)
53
+
54
+ def get_semantic_grep(self, space_id: str, *, query: str) -> Any:
55
+ if not query:
56
+ raise ValueError("query is required")
57
+ params = {"query": query}
58
+ return self._requester.request("GET", f"/space/{space_id}/semantic_grep", params=params)
@@ -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
@@ -1,98 +0,0 @@
1
- """
2
- Artifact and file endpoints.
3
- """
4
-
5
- import json
6
- from typing import Any, BinaryIO, Mapping, MutableMapping
7
-
8
- from ..client_types import RequesterProtocol
9
- from ..uploads import FileUpload, normalize_file_upload
10
-
11
-
12
- class ArtifactsAPI:
13
- def __init__(self, requester: RequesterProtocol) -> None:
14
- self._requester = requester
15
- self.files = ArtifactFilesAPI(requester)
16
-
17
- def list(self) -> Any:
18
- return self._requester.request("GET", "/artifact")
19
-
20
- def create(self) -> Any:
21
- return self._requester.request("POST", "/artifact")
22
-
23
- def delete(self, artifact_id: str) -> None:
24
- self._requester.request("DELETE", f"/artifact/{artifact_id}")
25
-
26
-
27
- class ArtifactFilesAPI:
28
- def __init__(self, requester: RequesterProtocol) -> None:
29
- self._requester = requester
30
-
31
- def upload(
32
- self,
33
- artifact_id: str,
34
- *,
35
- file: FileUpload | tuple[str, BinaryIO | bytes] | tuple[str, BinaryIO | bytes, str | None],
36
- file_path: str | None = None,
37
- meta: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
38
- ) -> Any:
39
- upload = normalize_file_upload(file)
40
- files = {"file": upload.as_httpx()}
41
- form: dict[str, Any] = {}
42
- if file_path:
43
- form["file_path"] = file_path
44
- if meta is not None:
45
- form["meta"] = json.dumps(meta)
46
- return self._requester.request(
47
- "POST",
48
- f"/artifact/{artifact_id}/file",
49
- data=form or None,
50
- files=files,
51
- )
52
-
53
- def update(
54
- self,
55
- artifact_id: str,
56
- *,
57
- file_path: str,
58
- file: FileUpload | tuple[str, BinaryIO | bytes] | tuple[str, BinaryIO | bytes, str | None],
59
- ) -> Any:
60
- upload = normalize_file_upload(file)
61
- files = {"file": upload.as_httpx()}
62
- form = {"file_path": file_path}
63
- return self._requester.request(
64
- "PUT",
65
- f"/artifact/{artifact_id}/file",
66
- data=form,
67
- files=files,
68
- )
69
-
70
- def delete(self, artifact_id: str, *, file_path: str) -> None:
71
- params = {"file_path": file_path}
72
- self._requester.request("DELETE", f"/artifact/{artifact_id}/file", params=params)
73
-
74
- def get(
75
- self,
76
- artifact_id: str,
77
- *,
78
- file_path: str,
79
- with_public_url: bool | None = None,
80
- expire: int | None = None,
81
- ) -> Any:
82
- params: dict[str, Any] = {"file_path": file_path}
83
- if with_public_url is not None:
84
- params["with_public_url"] = "true" if with_public_url else "false"
85
- if expire is not None:
86
- params["expire"] = expire
87
- return self._requester.request("GET", f"/artifact/{artifact_id}/file", params=params)
88
-
89
- def list(
90
- self,
91
- artifact_id: str,
92
- *,
93
- path: str | None = None,
94
- ) -> Any:
95
- params: dict[str, Any] = {}
96
- if path is not None:
97
- params["path"] = path
98
- return self._requester.request("GET", f"/artifact/{artifact_id}/file/ls", params=params or None)
@@ -1,82 +0,0 @@
1
- """
2
- Page endpoints.
3
- """
4
-
5
- from typing import Any, Mapping, MutableMapping
6
-
7
- from ..client_types import RequesterProtocol
8
-
9
-
10
- class PagesAPI:
11
- def __init__(self, requester: RequesterProtocol) -> None:
12
- self._requester = requester
13
-
14
- def list(self, space_id: str, *, parent_id: str | None = None) -> Any:
15
- params: dict[str, Any] = {}
16
- if parent_id is not None:
17
- params["parent_id"] = parent_id
18
- return self._requester.request("GET", f"/space/{space_id}/page", params=params or None)
19
-
20
- def create(
21
- self,
22
- space_id: str,
23
- *,
24
- parent_id: str | None = None,
25
- title: str | None = None,
26
- props: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
27
- ) -> Any:
28
- payload: dict[str, Any] = {}
29
- if parent_id is not None:
30
- payload["parent_id"] = parent_id
31
- if title is not None:
32
- payload["title"] = title
33
- if props is not None:
34
- payload["props"] = props
35
- return self._requester.request("POST", f"/space/{space_id}/page", json_data=payload)
36
-
37
- def delete(self, space_id: str, page_id: str) -> None:
38
- self._requester.request("DELETE", f"/space/{space_id}/page/{page_id}")
39
-
40
- def get_properties(self, space_id: str, page_id: str) -> Any:
41
- return self._requester.request("GET", f"/space/{space_id}/page/{page_id}/properties")
42
-
43
- def update_properties(
44
- self,
45
- space_id: str,
46
- page_id: str,
47
- *,
48
- title: str | None = None,
49
- props: Mapping[str, Any] | MutableMapping[str, Any] | None = None,
50
- ) -> None:
51
- payload: dict[str, Any] = {}
52
- if title is not None:
53
- payload["title"] = title
54
- if props is not None:
55
- payload["props"] = props
56
- if not payload:
57
- raise ValueError("title or props must be provided")
58
- self._requester.request("PUT", f"/space/{space_id}/page/{page_id}/properties", json_data=payload)
59
-
60
- def move(
61
- self,
62
- space_id: str,
63
- page_id: str,
64
- *,
65
- parent_id: str | None = None,
66
- sort: int | None = None,
67
- ) -> None:
68
- payload: dict[str, Any] = {}
69
- if parent_id is not None:
70
- payload["parent_id"] = parent_id
71
- if sort is not None:
72
- payload["sort"] = sort
73
- if not payload:
74
- raise ValueError("parent_id or sort must be provided")
75
- self._requester.request("PUT", f"/space/{space_id}/page/{page_id}/move", json_data=payload)
76
-
77
- def update_sort(self, space_id: str, page_id: str, *, sort: int) -> None:
78
- self._requester.request(
79
- "PUT",
80
- f"/space/{space_id}/page/{page_id}/sort",
81
- json_data={"sort": sort},
82
- )