databricks-mason 0.1.0.dev0__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.
@@ -0,0 +1,22 @@
1
+ # test
2
+ .pytest_cache/
3
+ mlruns/
4
+
5
+ # Byte-compiled files
6
+ __pycache__
7
+ *.pyc
8
+
9
+ # Editor files
10
+ *.vscode
11
+ .idea
12
+
13
+ # Mac
14
+ .DS_Store
15
+
16
+ # packaging
17
+ dist/
18
+ *.egg-info/
19
+ Pipfile
20
+ docs/build
21
+
22
+ **/uv.lock
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.5
2
+ Name: databricks-mason
3
+ Version: 0.1.0.dev0
4
+ Summary: Databricks integration for Mason
5
+ Author-email: Databricks <agent-feedback@databricks.com>
6
+ License: Apache-2.0
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: click>=8.1
9
+ Requires-Dist: databricks-sdk>=0.49
10
+ Requires-Dist: pyyaml>=6.0
11
+ Requires-Dist: rich>=13.7
12
+ Provides-Extra: tracing
13
+ Requires-Dist: mlflow[databricks]>=3.9.0; extra == 'tracing'
14
+ Description-Content-Type: text/markdown
15
+
16
+ # `databricks-mason`
17
+
18
+ Mason is an experimental CLI for Databricks custom agent preview APIs and
19
+ deployments. It manages memory, sessions, tracing, and deployments from one
20
+ authenticated command.
21
+
22
+ > The underlying APIs are in preview and may need workspace enablement.
23
+
24
+ ## Installation
25
+
26
+ From PyPI:
27
+
28
+ ```sh
29
+ pip install databricks-mason
30
+ ```
31
+
32
+ From source:
33
+
34
+ ```sh
35
+ pip install 'git+https://github.com/databricks/databricks-ai-bridge.git#subdirectory=integrations/mason'
36
+ ```
37
+
38
+ For tracing commands, install Mason with tracing extras:
39
+
40
+ ```sh
41
+ pip install 'databricks-mason[tracing]'
42
+ ```
43
+
44
+ ## Authentication
45
+
46
+ Mason uses [Databricks authentication](https://docs.databricks.com/aws/en/dev-tools/cli/authentication).
47
+ If you do not already have credentials, authenticate a named profile first. You can
48
+ then ask Mason to validate and remember that profile:
49
+
50
+ ```sh
51
+ databricks auth login --profile <profile>
52
+ mason login --profile <profile>
53
+ mason sessions stores list
54
+ ```
55
+
56
+ `mason login` does not create credentials; it stores the selected profile in
57
+ `~/.mason/config.json`. `mason logout` forgets that selection without revoking the
58
+ underlying credentials. If Databricks SDK default authentication is already configured,
59
+ you can skip `mason login`. You can also pass `--profile/-p` for an individual command.
60
+ Use `--output json` for scripting.
61
+
62
+ ## Commands
63
+
64
+ ```text
65
+ mason [-p <profile>] [-o text|json]
66
+ login [--profile P]
67
+ logout
68
+ memory
69
+ stores create | list | get | update | delete
70
+ entries create | get | list | search | update | delete
71
+ sessions create | list | get | update | delete | fork
72
+ stores create | list | get | update | delete
73
+ items list | append | pop | clear
74
+ tracing
75
+ setup --catalog C --schema S [--experiment E]
76
+ list | get | instrument
77
+ deploy <name> --source PATH [--with-memory-store N]
78
+ [--with-session-store N] [--with-traces C.S] [--create-stores]
79
+ deployments list | get | logs | start | stop | delete
80
+ ```
@@ -0,0 +1,65 @@
1
+ # `databricks-mason`
2
+
3
+ Mason is an experimental CLI for Databricks custom agent preview APIs and
4
+ deployments. It manages memory, sessions, tracing, and deployments from one
5
+ authenticated command.
6
+
7
+ > The underlying APIs are in preview and may need workspace enablement.
8
+
9
+ ## Installation
10
+
11
+ From PyPI:
12
+
13
+ ```sh
14
+ pip install databricks-mason
15
+ ```
16
+
17
+ From source:
18
+
19
+ ```sh
20
+ pip install 'git+https://github.com/databricks/databricks-ai-bridge.git#subdirectory=integrations/mason'
21
+ ```
22
+
23
+ For tracing commands, install Mason with tracing extras:
24
+
25
+ ```sh
26
+ pip install 'databricks-mason[tracing]'
27
+ ```
28
+
29
+ ## Authentication
30
+
31
+ Mason uses [Databricks authentication](https://docs.databricks.com/aws/en/dev-tools/cli/authentication).
32
+ If you do not already have credentials, authenticate a named profile first. You can
33
+ then ask Mason to validate and remember that profile:
34
+
35
+ ```sh
36
+ databricks auth login --profile <profile>
37
+ mason login --profile <profile>
38
+ mason sessions stores list
39
+ ```
40
+
41
+ `mason login` does not create credentials; it stores the selected profile in
42
+ `~/.mason/config.json`. `mason logout` forgets that selection without revoking the
43
+ underlying credentials. If Databricks SDK default authentication is already configured,
44
+ you can skip `mason login`. You can also pass `--profile/-p` for an individual command.
45
+ Use `--output json` for scripting.
46
+
47
+ ## Commands
48
+
49
+ ```text
50
+ mason [-p <profile>] [-o text|json]
51
+ login [--profile P]
52
+ logout
53
+ memory
54
+ stores create | list | get | update | delete
55
+ entries create | get | list | search | update | delete
56
+ sessions create | list | get | update | delete | fork
57
+ stores create | list | get | update | delete
58
+ items list | append | pop | clear
59
+ tracing
60
+ setup --catalog C --schema S [--experiment E]
61
+ list | get | instrument
62
+ deploy <name> --source PATH [--with-memory-store N]
63
+ [--with-session-store N] [--with-traces C.S] [--create-stores]
64
+ deployments list | get | logs | start | stop | delete
65
+ ```
@@ -0,0 +1,59 @@
1
+ [project]
2
+ name = "databricks-mason"
3
+ version = "0.1.0.dev0"
4
+ description = "Databricks integration for Mason"
5
+ authors = [
6
+ { name="Databricks", email="agent-feedback@databricks.com" },
7
+ ]
8
+ readme = "README.md"
9
+ license = { text="Apache-2.0" }
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "click>=8.1",
13
+ "databricks-sdk>=0.49",
14
+ "PyYAML>=6.0",
15
+ "rich>=13.7",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ tracing = [
20
+ "mlflow[databricks]>=3.9.0",
21
+ ]
22
+
23
+ [project.scripts]
24
+ mason = "databricks_mason.cli:main"
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ "ruff==0.14.10",
29
+ "ty==0.0.23",
30
+ { include-group = "tests" },
31
+ ]
32
+ tests = [
33
+ "pytest==9.0.2",
34
+ ]
35
+
36
+ [build-system]
37
+ requires = ["hatchling"]
38
+ build-backend = "hatchling.build"
39
+
40
+ [tool.hatch.build]
41
+ include = [
42
+ "src/databricks_mason/*",
43
+ ]
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/databricks_mason"]
47
+
48
+ [tool.ruff]
49
+ include = ["pyproject.toml", "src/**/*.py", "tests/**/*.py"]
50
+ extend = "../../pyproject.toml"
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests/unit_tests"]
54
+
55
+ [tool.ty.environment]
56
+ root = ["./src", "./tests"]
57
+
58
+ [tool.ty.src]
59
+ include = ["./src", "./tests"]
@@ -0,0 +1 @@
1
+ """Databricks integration for Mason."""
@@ -0,0 +1,83 @@
1
+ """`mason login` / `logout` — remember an optional Databricks profile.
2
+
3
+ `login` validates a named profile and persists the selection; the root group falls back
4
+ to it whenever `-p` is omitted. Without a saved profile, the Databricks SDK performs its
5
+ normal default authentication resolution. `logout` removes only Mason's saved selection,
6
+ not the underlying credentials. State lives in a small JSON file under `~/.mason`
7
+ (override the directory with `MASON_CONFIG_HOME`, mainly for tests).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import pathlib
15
+ from typing import Optional
16
+
17
+ import click
18
+
19
+ from databricks_mason import render
20
+ from databricks_mason.client import AgentApiClient
21
+ from databricks_mason.errors import AgentCliError
22
+
23
+
24
+ def _config_file() -> pathlib.Path:
25
+ base = os.environ.get("MASON_CONFIG_HOME")
26
+ root = pathlib.Path(base) if base else pathlib.Path.home() / ".mason"
27
+ return root / "config.json"
28
+
29
+
30
+ def load_default_profile() -> Optional[str]:
31
+ """The profile saved by `mason login`, or None if the user never logged in."""
32
+ try:
33
+ return json.loads(_config_file().read_text()).get("profile")
34
+ except (OSError, json.JSONDecodeError):
35
+ return None
36
+
37
+
38
+ def _save_default_profile(profile: str) -> None:
39
+ path = _config_file()
40
+ path.parent.mkdir(parents=True, exist_ok=True)
41
+ path.write_text(json.dumps({"profile": profile}, indent=2) + "\n")
42
+
43
+
44
+ @click.command()
45
+ @click.option(
46
+ "--profile",
47
+ "-p",
48
+ default=None,
49
+ help="Profile to authenticate with and remember as the default.",
50
+ )
51
+ @click.pass_obj
52
+ def login(obj, profile) -> None:
53
+ """Validate a profile's credentials and save it as the default, so later commands can omit -p."""
54
+ profile = profile or obj.profile
55
+ if not profile:
56
+ raise AgentCliError(
57
+ "No profile to save.",
58
+ hint="Pass one to remember, e.g. `mason login --profile my-workspace`.",
59
+ )
60
+ client = AgentApiClient(profile)
61
+ user = client.current_user # round-trips current_user.me(), so a bad profile fails here
62
+ _save_default_profile(profile)
63
+ if obj.output == "json":
64
+ render.emit_json({"profile": profile, "user": user, "host": client.host})
65
+ return
66
+ render.success(
67
+ f"Logged in as {user}",
68
+ fields={"Profile": profile, "Host": client.host},
69
+ next_steps=["mason sessions stores list", "mason memory stores list"],
70
+ )
71
+
72
+
73
+ @click.command()
74
+ @click.pass_obj
75
+ def logout(obj) -> None:
76
+ """Forget the saved profile selection without deleting its credentials."""
77
+ path = _config_file()
78
+ existed = path.exists()
79
+ path.unlink(missing_ok=True)
80
+ if obj.output == "json":
81
+ render.emit_json({"logged_out": existed})
82
+ return
83
+ render.success("Logged out" if existed else "No saved login to clear")
@@ -0,0 +1,72 @@
1
+ """`mason` — the Databricks CLI for agent deployment, memory, and sessions.
2
+
3
+ Root Click group. Global `--profile` and `--output` flow to every subcommand via
4
+ `CliContext` on `ctx.obj`; subcommands build an `AgentApiClient` from it on demand.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Optional
10
+
11
+ import click
12
+
13
+ from databricks_mason.auth import load_default_profile, login, logout
14
+ from databricks_mason.client import AgentApiClient
15
+ from databricks_mason.deploy import deploy, deployments
16
+ from databricks_mason.memory import memory
17
+ from databricks_mason.sessions import sessions
18
+ from databricks_mason.tracing import tracing
19
+
20
+
21
+ class CliContext:
22
+ """Shared per-invocation state: selected profile, output mode, lazily-built client."""
23
+
24
+ def __init__(self, profile: Optional[str], output: str):
25
+ self.profile = profile
26
+ self.output = output
27
+ self._client: Optional[AgentApiClient] = None
28
+
29
+ def client(self) -> AgentApiClient:
30
+ if self._client is None:
31
+ self._client = AgentApiClient(self.profile)
32
+ return self._client
33
+
34
+
35
+ @click.group(context_settings={"help_option_names": ["-h", "--help"]})
36
+ @click.option(
37
+ "--profile", "-p", default=None, help="~/.databrickscfg profile to authenticate with."
38
+ )
39
+ @click.option(
40
+ "--output",
41
+ "-o",
42
+ type=click.Choice(["text", "json"]),
43
+ default="text",
44
+ help="Output format (default: text).",
45
+ )
46
+ @click.version_option(package_name="databricks-mason", prog_name="mason")
47
+ @click.pass_context
48
+ def mason(ctx: click.Context, profile: Optional[str], output: str) -> None:
49
+ """Mason: deploy agents and manage their memory and sessions.
50
+
51
+ Targets the agents/v1 preview APIs served on a workspace; auth comes from a
52
+ .databrickscfg profile (pass --profile / -p, run `mason login` to save a default,
53
+ or rely on the SDK's default resolution).
54
+ """
55
+ ctx.obj = CliContext(profile=profile or load_default_profile(), output=output)
56
+
57
+
58
+ mason.add_command(login)
59
+ mason.add_command(logout)
60
+ mason.add_command(memory)
61
+ mason.add_command(sessions)
62
+ mason.add_command(tracing)
63
+ mason.add_command(deploy)
64
+ mason.add_command(deployments)
65
+
66
+
67
+ def main() -> None:
68
+ mason(prog_name="mason")
69
+
70
+
71
+ if __name__ == "__main__":
72
+ main()
@@ -0,0 +1,298 @@
1
+ """Authenticated REST client for the agents/v1 memory and session APIs.
2
+
3
+ Wraps a databricks-sdk `WorkspaceClient` so auth/host come from a `.databrickscfg`
4
+ profile. Each method maps to one API operation.
5
+ Deployment is handled separately (deploy.py) since it wraps the `databricks apps` CLI.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Optional
11
+
12
+ from databricks.sdk import WorkspaceClient
13
+
14
+ from databricks_mason.errors import AgentCliError, wrap_api_error
15
+
16
+ _BASE = "/api/agents/v1"
17
+
18
+
19
+ def _query(**kwargs: Any) -> dict[str, Any]:
20
+ """Build a query dict, dropping None and empty values."""
21
+ return {k: v for k, v in kwargs.items() if v is not None and v != ""}
22
+
23
+
24
+ def memory_store_path(name: str) -> str:
25
+ """Normalize a store id or name into the `memory-stores/{id}` resource segment."""
26
+ name = name.strip().strip("/")
27
+ return name if name.startswith("memory-stores/") else f"memory-stores/{name}"
28
+
29
+
30
+ def memory_entry_path(store: str, entry: str) -> str:
31
+ entry = entry.strip().strip("/")
32
+ if entry.startswith("memory-stores/"):
33
+ return entry
34
+ return f"{memory_store_path(store)}/entries/{entry}"
35
+
36
+
37
+ class AgentApiClient:
38
+ """Thin, authenticated wrapper over the agents/v1 REST surface."""
39
+
40
+ def __init__(self, profile: Optional[str] = None):
41
+ try:
42
+ self._w = WorkspaceClient(profile=profile)
43
+ except Exception as exc: # noqa: BLE001 - surfaced as a clean CLI error
44
+ raise AgentCliError(
45
+ f"Could not initialize Databricks auth: {exc}",
46
+ hint="Check your profile (`databricks auth login --profile <name>`) "
47
+ "or pass --profile.",
48
+ ) from exc
49
+
50
+ @property
51
+ def host(self) -> str:
52
+ return self._w.config.host or "unknown"
53
+
54
+ @property
55
+ def current_user(self) -> str:
56
+ """The authenticated user's name (used to derive the app source workspace path)."""
57
+ return str(self._w.current_user.me().user_name or "unknown")
58
+
59
+ def _do(
60
+ self, method: str, path: str, *, query: Optional[dict] = None, body: Optional[dict] = None
61
+ ) -> Any:
62
+ try:
63
+ return self._w.api_client.do(method, path, query=query, body=body)
64
+ except Exception as exc: # noqa: BLE001 - normalized to AgentCliError
65
+ raise wrap_api_error(exc) from exc
66
+
67
+ # --- memory stores -------------------------------------------------------
68
+
69
+ def create_memory_store(self, display_name: str, description: Optional[str] = None) -> dict:
70
+ body = _query(display_name=display_name, description=description)
71
+ return self._do("POST", f"{_BASE}/memory-stores", body=body)
72
+
73
+ def get_memory_store(self, name: str) -> dict:
74
+ return self._do("GET", f"{_BASE}/{memory_store_path(name)}")
75
+
76
+ def list_memory_stores(
77
+ self, page_size: Optional[int] = None, page_token: Optional[str] = None
78
+ ) -> dict:
79
+ return self._do(
80
+ "GET",
81
+ f"{_BASE}/memory-stores",
82
+ query=_query(page_size=page_size, page_token=page_token),
83
+ )
84
+
85
+ def update_memory_store(
86
+ self, name: str, display_name: Optional[str] = None, description: Optional[str] = None
87
+ ) -> dict:
88
+ body = _query(display_name=display_name, description=description)
89
+ mask = ",".join(body.keys())
90
+ return self._do(
91
+ "PATCH", f"{_BASE}/{memory_store_path(name)}", query=_query(update_mask=mask), body=body
92
+ )
93
+
94
+ def delete_memory_store(self, name: str) -> dict:
95
+ return self._do("DELETE", f"{_BASE}/{memory_store_path(name)}")
96
+
97
+ # --- memory entries ------------------------------------------------------
98
+
99
+ def create_memory_entry(
100
+ self,
101
+ store: str,
102
+ actor_id: str,
103
+ path: str,
104
+ content: Optional[str] = None,
105
+ description: Optional[str] = None,
106
+ session_id: Optional[str] = None,
107
+ source_type: Optional[str] = None,
108
+ ) -> dict:
109
+ body = _query(
110
+ actor_id=actor_id,
111
+ path=path,
112
+ content=content,
113
+ description=description,
114
+ session_id=session_id,
115
+ source_type=source_type,
116
+ )
117
+ return self._do("POST", f"{_BASE}/{memory_store_path(store)}/entries", body=body)
118
+
119
+ def get_memory_entry(self, store: str, entry: str) -> dict:
120
+ return self._do("GET", f"{_BASE}/{memory_entry_path(store, entry)}")
121
+
122
+ def list_memory_entries(
123
+ self,
124
+ store: str,
125
+ actor_id: str,
126
+ path_prefix: Optional[str] = None,
127
+ session_id: Optional[str] = None,
128
+ page_size: Optional[int] = None,
129
+ page_token: Optional[str] = None,
130
+ ) -> dict:
131
+ return self._do(
132
+ "GET",
133
+ f"{_BASE}/{memory_store_path(store)}/entries",
134
+ query=_query(
135
+ actor_id=actor_id,
136
+ path_prefix=path_prefix,
137
+ session_id=session_id,
138
+ page_size=page_size,
139
+ page_token=page_token,
140
+ ),
141
+ )
142
+
143
+ def search_memory_entries(
144
+ self, store: str, actor_id: str, query: str, limit: Optional[int] = None
145
+ ) -> dict:
146
+ body = _query(actor_id=actor_id, query=query, limit=limit)
147
+ return self._do("POST", f"{_BASE}/{memory_store_path(store)}/entries:search", body=body)
148
+
149
+ def update_memory_entry(
150
+ self,
151
+ store: str,
152
+ entry: str,
153
+ content: Optional[str] = None,
154
+ description: Optional[str] = None,
155
+ ) -> dict:
156
+ body = _query(content=content, description=description)
157
+ return self._do("PATCH", f"{_BASE}/{memory_entry_path(store, entry)}", body=body)
158
+
159
+ def delete_memory_entry(self, store: str, entry: str) -> dict:
160
+ return self._do("DELETE", f"{_BASE}/{memory_entry_path(store, entry)}")
161
+
162
+ # --- session stores ------------------------------------------------------
163
+
164
+ def create_session_store(
165
+ self, name: str, description: Optional[str] = None, metadata: Optional[dict] = None
166
+ ) -> dict:
167
+ body = _query(description=description, metadata=metadata)
168
+ return self._do(
169
+ "POST", f"{_BASE}/session-stores", query={"session_store_name": name}, body=body
170
+ )
171
+
172
+ def get_session_store(self, name: str) -> dict:
173
+ return self._do("GET", f"{_BASE}/session-stores/{name}")
174
+
175
+ def list_session_stores(
176
+ self, page_size: Optional[int] = None, page_token: Optional[str] = None
177
+ ) -> dict:
178
+ return self._do(
179
+ "GET",
180
+ f"{_BASE}/session-stores",
181
+ query=_query(page_size=page_size, page_token=page_token),
182
+ )
183
+
184
+ def update_session_store(
185
+ self, name: str, description: Optional[str] = None, metadata: Optional[dict] = None
186
+ ) -> dict:
187
+ body = _query(description=description, metadata=metadata)
188
+ mask = ",".join(body.keys())
189
+ return self._do(
190
+ "PATCH", f"{_BASE}/session-stores/{name}", query=_query(update_mask=mask), body=body
191
+ )
192
+
193
+ def delete_session_store(self, name: str) -> dict:
194
+ return self._do("DELETE", f"{_BASE}/session-stores/{name}")
195
+
196
+ # --- sessions ------------------------------------------------------------
197
+
198
+ def create_session(
199
+ self,
200
+ store: str,
201
+ actor_id: str,
202
+ session_id: Optional[str] = None,
203
+ parent_session_id: Optional[str] = None,
204
+ metadata: Optional[dict] = None,
205
+ ) -> dict:
206
+ body = _query(actor_id=actor_id, parent_session_id=parent_session_id, metadata=metadata)
207
+ return self._do(
208
+ "POST",
209
+ f"{_BASE}/session-stores/{store}/sessions",
210
+ query=_query(session_id=session_id),
211
+ body=body,
212
+ )
213
+
214
+ def list_sessions(
215
+ self,
216
+ store: str,
217
+ filter: Optional[str] = None,
218
+ order_by: Optional[str] = None,
219
+ page_size: Optional[int] = None,
220
+ page_token: Optional[str] = None,
221
+ ) -> dict:
222
+ return self._do(
223
+ "GET",
224
+ f"{_BASE}/session-stores/{store}/sessions",
225
+ query=_query(
226
+ filter=filter, order_by=order_by, page_size=page_size, page_token=page_token
227
+ ),
228
+ )
229
+
230
+ def get_session(self, session_id: str, store: Optional[str] = None) -> dict:
231
+ if store:
232
+ return self._do("GET", f"{_BASE}/session-stores/{store}/sessions/{session_id}")
233
+ return self._do("GET", f"{_BASE}/sessions/{session_id}")
234
+
235
+ def update_session(self, store: str, session_id: str, metadata: dict) -> dict:
236
+ return self._do(
237
+ "PATCH",
238
+ f"{_BASE}/session-stores/{store}/sessions/{session_id}",
239
+ query={"update_mask": "metadata"},
240
+ body=_query(metadata=metadata),
241
+ )
242
+
243
+ def delete_session(self, store: str, session_id: str, force: bool = False) -> dict:
244
+ return self._do(
245
+ "DELETE",
246
+ f"{_BASE}/session-stores/{store}/sessions/{session_id}",
247
+ query=_query(force=force or None),
248
+ )
249
+
250
+ def fork_session(
251
+ self,
252
+ store: str,
253
+ source_session_id: str,
254
+ actor_id: str,
255
+ up_to_item_id: Optional[str] = None,
256
+ session_id: Optional[str] = None,
257
+ metadata: Optional[dict] = None,
258
+ ) -> dict:
259
+ body = _query(
260
+ source_session_id=source_session_id,
261
+ actor_id=actor_id,
262
+ up_to_item_id=up_to_item_id,
263
+ session_id=session_id,
264
+ metadata=metadata,
265
+ )
266
+ return self._do("POST", f"{_BASE}/session-stores/{store}/sessions:fork", body=body)
267
+
268
+ # --- session items -------------------------------------------------------
269
+
270
+ def list_session_items(
271
+ self,
272
+ store: str,
273
+ session_id: str,
274
+ order_by: Optional[str] = None,
275
+ page_size: Optional[int] = None,
276
+ page_token: Optional[str] = None,
277
+ ) -> dict:
278
+ return self._do(
279
+ "GET",
280
+ f"{_BASE}/session-stores/{store}/sessions/{session_id}/items",
281
+ query=_query(order_by=order_by, page_size=page_size, page_token=page_token),
282
+ )
283
+
284
+ def append_session_items(self, store: str, session_id: str, items: list[dict]) -> dict:
285
+ body = {"items": [{"data": item} for item in items]}
286
+ return self._do(
287
+ "POST", f"{_BASE}/session-stores/{store}/sessions/{session_id}/items:append", body=body
288
+ )
289
+
290
+ def pop_session_item(self, store: str, session_id: str) -> dict:
291
+ return self._do(
292
+ "POST", f"{_BASE}/session-stores/{store}/sessions/{session_id}/items:pop", body={}
293
+ )
294
+
295
+ def clear_session_items(self, store: str, session_id: str) -> dict:
296
+ return self._do(
297
+ "POST", f"{_BASE}/session-stores/{store}/sessions/{session_id}/items:clear", body={}
298
+ )