databricks-agentbricks 0.2.0__py3-none-any.whl
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.
- databricks_agentbricks/__init__.py +1 -0
- databricks_agentbricks/_group.py +217 -0
- databricks_agentbricks/agent_project.py +657 -0
- databricks_agentbricks/app_resources.py +139 -0
- databricks_agentbricks/cli/__init__.py +12 -0
- databricks_agentbricks/cli/app.py +108 -0
- databricks_agentbricks/cli/app_auth.py +198 -0
- databricks_agentbricks/cli/auth.py +142 -0
- databricks_agentbricks/cli/deploy.py +954 -0
- databricks_agentbricks/cli/dev.py +310 -0
- databricks_agentbricks/cli/endpoint.py +145 -0
- databricks_agentbricks/cli/endpoint_examples.py +33 -0
- databricks_agentbricks/cli/endpoint_output.py +53 -0
- databricks_agentbricks/cli/endpoint_request.py +71 -0
- databricks_agentbricks/cli/endpoint_transport.py +148 -0
- databricks_agentbricks/cli/help.py +467 -0
- databricks_agentbricks/cli/init.py +496 -0
- databricks_agentbricks/cli/mcp.py +75 -0
- databricks_agentbricks/cli/memory.py +520 -0
- databricks_agentbricks/cli/sandbox.py +823 -0
- databricks_agentbricks/cli/sessions.py +588 -0
- databricks_agentbricks/cli/tools.py +456 -0
- databricks_agentbricks/cli/tracing.py +724 -0
- databricks_agentbricks/databricks_cli.py +38 -0
- databricks_agentbricks/errors.py +114 -0
- databricks_agentbricks/lakebase_runtime_store.py +107 -0
- databricks_agentbricks/legacy_lakebase_runtime_store.py +74 -0
- databricks_agentbricks/project_config.py +157 -0
- databricks_agentbricks/project_types.py +55 -0
- databricks_agentbricks/py.typed +0 -0
- databricks_agentbricks/render.py +349 -0
- databricks_agentbricks/templates/agent-bricks-migrate/SKILL.md +101 -0
- databricks_agentbricks/templates/agent-langgraph/.env.example +36 -0
- databricks_agentbricks/templates/agent-langgraph/.gitignore +199 -0
- databricks_agentbricks/templates/agent-langgraph/AGENTKIT_CONTRACT.md +95 -0
- databricks_agentbricks/templates/agent-langgraph/AGENTS.md +31 -0
- databricks_agentbricks/templates/agent-langgraph/CLAUDE.md +1 -0
- databricks_agentbricks/templates/agent-langgraph/README.md +141 -0
- databricks_agentbricks/templates/agent-langgraph/agent/__init__.py +0 -0
- databricks_agentbricks/templates/agent-langgraph/agent/agent.py +177 -0
- databricks_agentbricks/templates/agent-langgraph/agent/mcps.py +27 -0
- databricks_agentbricks/templates/agent-langgraph/agent/tools/__init__.py +24 -0
- databricks_agentbricks/templates/agent-langgraph/agent/tools/sample_tool.py +15 -0
- databricks_agentbricks/templates/agent-langgraph/agent/tools/send_message.py +17 -0
- databricks_agentbricks/templates/agent-langgraph/app.yaml +15 -0
- databricks_agentbricks/templates/agent-langgraph/pyproject.toml +42 -0
- databricks_agentbricks/templates/agent-langgraph/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/agent-langgraph/runtime/adapter.py +144 -0
- databricks_agentbricks/templates/agent-langgraph/runtime/main.py +25 -0
- databricks_agentbricks/templates/agent-langgraph/tests/__init__.py +0 -0
- databricks_agentbricks/templates/agent-langgraph/tests/test_agent.py +211 -0
- databricks_agentbricks/templates/agent-langgraph/tests/test_runtime.py +19 -0
- databricks_agentbricks/templates/agent-langgraph/tests/test_workspace.py +28 -0
- databricks_agentbricks/templates/agent-openai/.env.example +37 -0
- databricks_agentbricks/templates/agent-openai/.gitignore +199 -0
- databricks_agentbricks/templates/agent-openai/AGENTKIT_CONTRACT.md +107 -0
- databricks_agentbricks/templates/agent-openai/AGENTS.md +76 -0
- databricks_agentbricks/templates/agent-openai/CLAUDE.md +1 -0
- databricks_agentbricks/templates/agent-openai/README.md +143 -0
- databricks_agentbricks/templates/agent-openai/agent/__init__.py +0 -0
- databricks_agentbricks/templates/agent-openai/agent/agent.py +205 -0
- databricks_agentbricks/templates/agent-openai/agent/mcps.py +28 -0
- databricks_agentbricks/templates/agent-openai/agent/tools/__init__.py +24 -0
- databricks_agentbricks/templates/agent-openai/agent/tools/sample_tool.py +15 -0
- databricks_agentbricks/templates/agent-openai/agent/tools/send_message.py +18 -0
- databricks_agentbricks/templates/agent-openai/app.yaml +15 -0
- databricks_agentbricks/templates/agent-openai/pyproject.toml +42 -0
- databricks_agentbricks/templates/agent-openai/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/agent-openai/runtime/adapter.py +188 -0
- databricks_agentbricks/templates/agent-openai/runtime/main.py +25 -0
- databricks_agentbricks/templates/agent-openai/tests/__init__.py +0 -0
- databricks_agentbricks/templates/agent-openai/tests/test_agent.py +326 -0
- databricks_agentbricks/templates/agent-openai/tests/test_runtime.py +19 -0
- databricks_agentbricks/templates/agent-openai/tests/test_workspace.py +28 -0
- databricks_agentbricks/templates/custom-agent-langgraph/.env.example +1 -0
- databricks_agentbricks/templates/custom-agent-langgraph/.gitignore +11 -0
- databricks_agentbricks/templates/custom-agent-langgraph/README.md +22 -0
- databricks_agentbricks/templates/custom-agent-langgraph/agent/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-langgraph/agent/agent.py +27 -0
- databricks_agentbricks/templates/custom-agent-langgraph/app.yaml +1 -0
- databricks_agentbricks/templates/custom-agent-langgraph/pyproject.toml +38 -0
- databricks_agentbricks/templates/custom-agent-langgraph/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-langgraph/runtime/main.py +31 -0
- databricks_agentbricks/templates/custom-agent-langgraph/tests/test_runtime.py +29 -0
- databricks_agentbricks/templates/custom-agent-openai/.env.example +1 -0
- databricks_agentbricks/templates/custom-agent-openai/.gitignore +11 -0
- databricks_agentbricks/templates/custom-agent-openai/README.md +22 -0
- databricks_agentbricks/templates/custom-agent-openai/agent/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-openai/agent/agent.py +29 -0
- databricks_agentbricks/templates/custom-agent-openai/app.yaml +1 -0
- databricks_agentbricks/templates/custom-agent-openai/pyproject.toml +38 -0
- databricks_agentbricks/templates/custom-agent-openai/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-openai/runtime/main.py +31 -0
- databricks_agentbricks/templates/custom-agent-openai/tests/test_runtime.py +29 -0
- databricks_agentbricks/templates/sandbox_mcp.py +39 -0
- databricks_agentbricks/templates/sandbox_mcp_langgraph.py +39 -0
- databricks_agentbricks/templates/ui/agent-langgraph/CHAT_APP.md +49 -0
- databricks_agentbricks/templates/ui/agent-langgraph/runtime/main.py +26 -0
- databricks_agentbricks/templates/ui/agent-langgraph/runtime/ui.py +528 -0
- databricks_agentbricks/templates/ui/agent-langgraph/tests/test_demo_ui.py +542 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/agentbricks-mark.png +0 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/app.js +1464 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/index.html +281 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/styles.css +1913 -0
- databricks_agentbricks/templates/ui/agent-openai/CHAT_APP.md +54 -0
- databricks_agentbricks/templates/ui/agent-openai/runtime/main.py +26 -0
- databricks_agentbricks/templates/ui/agent-openai/runtime/ui.py +521 -0
- databricks_agentbricks/templates/ui/agent-openai/tests/test_demo_ui.py +512 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/agentbricks-mark.png +0 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/app.js +1428 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/index.html +281 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/styles.css +1913 -0
- databricks_agentbricks/theme.py +72 -0
- databricks_agentbricks-0.2.0.dist-info/METADATA +897 -0
- databricks_agentbricks-0.2.0.dist-info/RECORD +159 -0
- databricks_agentbricks-0.2.0.dist-info/WHEEL +4 -0
- databricks_agentbricks-0.2.0.dist-info/entry_points.txt +2 -0
- databricks_agentbricks-0.2.0.dist-info/licenses/NOTICE +39 -0
- databricks_agentkit/__init__.py +67 -0
- databricks_agentkit/_api_client.py +673 -0
- databricks_agentkit/_pagination.py +25 -0
- databricks_agentkit/client.py +31 -0
- databricks_agentkit/langgraph/__init__.py +98 -0
- databricks_agentkit/langgraph/genie.py +50 -0
- databricks_agentkit/langgraph/mcp.py +216 -0
- databricks_agentkit/langgraph/memory.py +67 -0
- databricks_agentkit/langgraph/session_store.py +485 -0
- databricks_agentkit/memory_store.py +371 -0
- databricks_agentkit/models.py +330 -0
- databricks_agentkit/openai/__init__.py +93 -0
- databricks_agentkit/openai/genie.py +50 -0
- databricks_agentkit/openai/mcp.py +189 -0
- databricks_agentkit/openai/memory.py +67 -0
- databricks_agentkit/openai/sessions.py +157 -0
- databricks_agentkit/py.typed +0 -0
- databricks_agentkit/runtime/ARCHITECTURE.md +64 -0
- databricks_agentkit/runtime/README.md +184 -0
- databricks_agentkit/runtime/__init__.py +69 -0
- databricks_agentkit/runtime/app.py +316 -0
- databricks_agentkit/runtime/auth.py +149 -0
- databricks_agentkit/runtime/durability/__init__.py +1 -0
- databricks_agentkit/runtime/durability/execution.py +133 -0
- databricks_agentkit/runtime/durability/heartbeat.py +64 -0
- databricks_agentkit/runtime/durability/lakebase_runtime_store.py +648 -0
- databricks_agentkit/runtime/durability/recovery.py +70 -0
- databricks_agentkit/runtime/durability/store.py +33 -0
- databricks_agentkit/runtime/execution.py +160 -0
- databricks_agentkit/runtime/genie.py +224 -0
- databricks_agentkit/runtime/mcp_auth.py +114 -0
- databricks_agentkit/runtime/model_services.py +90 -0
- databricks_agentkit/runtime/runtime.py +273 -0
- databricks_agentkit/runtime/session_store_client.py +120 -0
- databricks_agentkit/runtime/store.py +287 -0
- databricks_agentkit/runtime/tool_manifest.py +223 -0
- databricks_agentkit/runtime/tracing.py +71 -0
- databricks_agentkit/runtime/types.py +118 -0
- databricks_agentkit/runtime/workspace.py +27 -0
- databricks_agentkit/session_store.py +434 -0
- databricks_agentkit/timefmt.py +85 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
"""`ab sessions` — manage session stores, sessions, and session items."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import pathlib
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from databricks_agentbricks import render
|
|
13
|
+
from databricks_agentbricks.errors import AgentCliError
|
|
14
|
+
from databricks_agentbricks.render import field
|
|
15
|
+
from databricks_agentkit import timefmt
|
|
16
|
+
|
|
17
|
+
_BREADCRUMB = "Agent Session"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _truncate(value: Any, length: int = 60) -> str:
|
|
21
|
+
text = "" if value is None else (value if isinstance(value, str) else json.dumps(value))
|
|
22
|
+
return text if len(text) <= length else text[: length - 1] + "…"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# --- group ------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.group()
|
|
29
|
+
def sessions() -> None:
|
|
30
|
+
"""Manage an agent's conversations: session stores, the sessions in them, and their items.
|
|
31
|
+
|
|
32
|
+
A session is a single conversation between an actor (a user) and the agent. It holds that
|
|
33
|
+
conversation's ordered transcript of items — the messages, tool calls, and results that make up
|
|
34
|
+
its running state. A session store is the managed store that holds an agent's sessions and their
|
|
35
|
+
items, giving it durable conversation history it can list, resume, fork, or delete.
|
|
36
|
+
|
|
37
|
+
This is the short-term, per-conversation counterpart to the cross-conversation memory in
|
|
38
|
+
`ab memory`.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@sessions.group()
|
|
43
|
+
def stores() -> None:
|
|
44
|
+
"""Workspace-scoped session stores."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@sessions.group()
|
|
48
|
+
def items() -> None:
|
|
49
|
+
"""Transcript items within a session."""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# --- bind a store to an agent project (agent.toml) --------------------------
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _source_option(function):
|
|
56
|
+
return click.option(
|
|
57
|
+
"--source",
|
|
58
|
+
type=click.Path(exists=True, file_okay=False, path_type=pathlib.Path),
|
|
59
|
+
default=pathlib.Path("."),
|
|
60
|
+
show_default=True,
|
|
61
|
+
help="Agent Bricks project containing agent.toml.",
|
|
62
|
+
)(function)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@sessions.command("bind")
|
|
66
|
+
@click.argument("store")
|
|
67
|
+
@_source_option
|
|
68
|
+
@click.pass_obj
|
|
69
|
+
def sessions_bind(obj, store: str, source: pathlib.Path) -> None:
|
|
70
|
+
"""Bind session STORE to the agent by declaring it in agent.toml.
|
|
71
|
+
|
|
72
|
+
This only edits agent.toml — it does not create the store. `ab deploy` creates any declared
|
|
73
|
+
store that doesn't exist yet and grants the deployed app's service principal access to it.
|
|
74
|
+
"""
|
|
75
|
+
from databricks_agentbricks.agent_project import AgentProject
|
|
76
|
+
|
|
77
|
+
project = AgentProject.load(source)
|
|
78
|
+
project.bind_session_store(store)
|
|
79
|
+
project.write()
|
|
80
|
+
if obj.output == "json":
|
|
81
|
+
render.emit_json({"session_store": store, "manifest": str(project.path)})
|
|
82
|
+
return
|
|
83
|
+
render.success(
|
|
84
|
+
f"Bound session store '{store}'",
|
|
85
|
+
fields={"agent.toml": str(project.path)},
|
|
86
|
+
next_steps=[
|
|
87
|
+
(
|
|
88
|
+
f"ab sessions stores create --name {store}",
|
|
89
|
+
"Create the store now without deploying",
|
|
90
|
+
),
|
|
91
|
+
("ab dev", "Re-run to pick up the store locally"),
|
|
92
|
+
("ab deploy <name>", "Create it if missing and grant the app access"),
|
|
93
|
+
],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@sessions.command("unbind")
|
|
98
|
+
@_source_option
|
|
99
|
+
@click.pass_obj
|
|
100
|
+
def sessions_unbind(obj, source: pathlib.Path) -> None:
|
|
101
|
+
"""Remove the session store binding from the agent's agent.toml.
|
|
102
|
+
|
|
103
|
+
Only edits agent.toml; the managed store itself is untouched (delete it with
|
|
104
|
+
`ab sessions stores delete`).
|
|
105
|
+
"""
|
|
106
|
+
from databricks_agentbricks.agent_project import AgentProject
|
|
107
|
+
|
|
108
|
+
project = AgentProject.load(source)
|
|
109
|
+
if project.unbind_session_store():
|
|
110
|
+
project.write()
|
|
111
|
+
render.success("Removed session store binding", fields={"agent.toml": str(project.path)})
|
|
112
|
+
else:
|
|
113
|
+
click.echo(f"No session store binding in {project.path}.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# --- session stores ---------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _render_store_detail(store: dict) -> None:
|
|
120
|
+
name = field(store, "session_store_name")
|
|
121
|
+
render.detail(
|
|
122
|
+
f"{_BREADCRUMB} Store",
|
|
123
|
+
name or "—",
|
|
124
|
+
{
|
|
125
|
+
"Name": name,
|
|
126
|
+
"Resource name": f"session-stores/{name}" if name else None,
|
|
127
|
+
"Store ID": field(store, "session_store_id"),
|
|
128
|
+
"Creator": field(store, "creator_user_id"),
|
|
129
|
+
"Storage": render.field(field(store, "storage_backend") or {}, "backend_id"),
|
|
130
|
+
"Description": field(store, "description"),
|
|
131
|
+
"Created": timefmt.absolute(field(store, "create_time")),
|
|
132
|
+
"Updated": timefmt.absolute(field(store, "update_time")),
|
|
133
|
+
},
|
|
134
|
+
status="ACTIVE",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@stores.command("create")
|
|
139
|
+
@click.option(
|
|
140
|
+
"--name",
|
|
141
|
+
"--display-name",
|
|
142
|
+
"name",
|
|
143
|
+
required=True,
|
|
144
|
+
help="Workspace-unique store name, 3-63 chars (--display-name is accepted as an alias).",
|
|
145
|
+
)
|
|
146
|
+
@click.option("--description", default=None)
|
|
147
|
+
@click.option("--metadata", default=None, help="JSON object of string labels.")
|
|
148
|
+
@click.pass_obj
|
|
149
|
+
def stores_create(obj, name, description, metadata) -> None:
|
|
150
|
+
"""Create a session store."""
|
|
151
|
+
with render.status(f"Creating session store '{name}'…"):
|
|
152
|
+
data = obj.client().create_session_store(name, description, _parse_metadata(metadata))
|
|
153
|
+
if obj.output == "json":
|
|
154
|
+
render.emit_json(data)
|
|
155
|
+
return
|
|
156
|
+
render.success(
|
|
157
|
+
f"Created session store '{name}'",
|
|
158
|
+
fields={"Store ID": field(data, "session_store_id")},
|
|
159
|
+
next_steps=[
|
|
160
|
+
(
|
|
161
|
+
f"ab sessions create --store {name} --actor-id <id>",
|
|
162
|
+
"Start a session for an actor",
|
|
163
|
+
),
|
|
164
|
+
(f"ab sessions stores get {name}", "View this store's details"),
|
|
165
|
+
(
|
|
166
|
+
f"ab sessions bind {name}",
|
|
167
|
+
"Bind this store to the agent (wired in on dev/deploy)",
|
|
168
|
+
),
|
|
169
|
+
],
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@stores.command("list")
|
|
174
|
+
@click.option("--page-size", type=int, default=25, show_default=True)
|
|
175
|
+
@click.option("--page-token", default=None)
|
|
176
|
+
@click.pass_obj
|
|
177
|
+
def stores_list(obj, page_size, page_token) -> None:
|
|
178
|
+
"""List session stores in the workspace (25 per page; paginates interactively on a terminal)."""
|
|
179
|
+
client = obj.client()
|
|
180
|
+
if obj.output == "json":
|
|
181
|
+
render.emit_json(client.list_session_stores(page_size, page_token))
|
|
182
|
+
return
|
|
183
|
+
# On a terminal, offer to fetch the next page instead of only printing the --page-token hint.
|
|
184
|
+
interactive = sys.stdin.isatty() and sys.stdout.isatty()
|
|
185
|
+
while True:
|
|
186
|
+
data = client.list_session_stores(page_size, page_token)
|
|
187
|
+
items_ = field(data, "session_stores") or []
|
|
188
|
+
rows = [
|
|
189
|
+
[
|
|
190
|
+
field(s, "session_store_name"),
|
|
191
|
+
timefmt.relative(field(s, "create_time")),
|
|
192
|
+
timefmt.relative(field(s, "update_time")),
|
|
193
|
+
_truncate(field(s, "description"), 40),
|
|
194
|
+
]
|
|
195
|
+
for s in items_
|
|
196
|
+
]
|
|
197
|
+
token = field(data, "next_page_token")
|
|
198
|
+
render.resource_table(
|
|
199
|
+
"Session Stores",
|
|
200
|
+
[
|
|
201
|
+
# A session store's resource name is its workspace-unique, human-readable name.
|
|
202
|
+
("Resource name", "left"),
|
|
203
|
+
("Created", "left"),
|
|
204
|
+
("Updated", "left"),
|
|
205
|
+
("Description", "left"),
|
|
206
|
+
],
|
|
207
|
+
rows,
|
|
208
|
+
# In an interactive session the prompt below replaces the token hint.
|
|
209
|
+
subtitle=None if (interactive and token) else _page_note(data),
|
|
210
|
+
no_wrap=[0], # keep the resource name full-width; other columns narrow to fit
|
|
211
|
+
)
|
|
212
|
+
if not token or not interactive:
|
|
213
|
+
break
|
|
214
|
+
if not click.confirm("Show next page?", default=False):
|
|
215
|
+
break
|
|
216
|
+
page_token = token
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@stores.command("get")
|
|
220
|
+
@click.argument("name")
|
|
221
|
+
@click.pass_obj
|
|
222
|
+
def stores_get(obj, name) -> None:
|
|
223
|
+
"""Get a session store by name."""
|
|
224
|
+
data = obj.client().get_session_store(name)
|
|
225
|
+
if obj.output == "json":
|
|
226
|
+
render.emit_json(data)
|
|
227
|
+
return
|
|
228
|
+
_render_store_detail(data)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@stores.command("update")
|
|
232
|
+
@click.argument("name")
|
|
233
|
+
@click.option("--description", default=None)
|
|
234
|
+
@click.option("--metadata", default=None, help="JSON object of string labels.")
|
|
235
|
+
@click.pass_obj
|
|
236
|
+
def stores_update(obj, name, description, metadata) -> None:
|
|
237
|
+
"""Update a store's description and/or metadata."""
|
|
238
|
+
data = obj.client().update_session_store(name, description, _parse_metadata(metadata))
|
|
239
|
+
if obj.output == "json":
|
|
240
|
+
render.emit_json(data)
|
|
241
|
+
return
|
|
242
|
+
_render_store_detail(data)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@stores.command("delete")
|
|
246
|
+
@click.argument("name")
|
|
247
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt.")
|
|
248
|
+
@click.pass_obj
|
|
249
|
+
def stores_delete(obj, name, yes) -> None:
|
|
250
|
+
"""Delete a session store."""
|
|
251
|
+
render.confirm_destroy(f"session store '{name}'", assume_yes=yes)
|
|
252
|
+
obj.client().delete_session_store(name)
|
|
253
|
+
if obj.output == "json":
|
|
254
|
+
render.emit_json({"deleted": name})
|
|
255
|
+
return
|
|
256
|
+
render.success(f"Deleted session store '{name}'")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# --- sessions ---------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _session_starter_code(obj, store: str, session_id: str) -> list[tuple[str, str, str]]:
|
|
263
|
+
return [
|
|
264
|
+
(
|
|
265
|
+
"curl",
|
|
266
|
+
"bash",
|
|
267
|
+
f"""
|
|
268
|
+
curl -X POST "{obj.client().host}/api/2.0/agents/session-stores/{store}/sessions/{session_id}/items:append" \\
|
|
269
|
+
-H "Authorization: Bearer $DATABRICKS_TOKEN" -H "Content-Type: application/json" \\
|
|
270
|
+
-d '{{"items": [{{"data": {{"role": "user", "content": "Hello"}}}}]}}'
|
|
271
|
+
""",
|
|
272
|
+
),
|
|
273
|
+
(
|
|
274
|
+
"agentbricks",
|
|
275
|
+
"bash",
|
|
276
|
+
f"""
|
|
277
|
+
ab sessions items append --store {store} --session-id {session_id} \\
|
|
278
|
+
--data '{{"role": "user", "content": "Hello"}}'
|
|
279
|
+
ab sessions items list --store {store} --session-id {session_id}
|
|
280
|
+
""",
|
|
281
|
+
),
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _render_session_detail(obj, session: dict, store: Optional[str]) -> None:
|
|
286
|
+
store = store or field(session, "session_store_name")
|
|
287
|
+
session_id = field(session, "session_id")
|
|
288
|
+
render.detail(
|
|
289
|
+
_BREADCRUMB,
|
|
290
|
+
session_id or "—",
|
|
291
|
+
{
|
|
292
|
+
"Session ID": session_id,
|
|
293
|
+
"Store": field(session, "session_store_name") or store,
|
|
294
|
+
"Actor": field(session, "actor_id"),
|
|
295
|
+
"Parent": field(session, "parent_session_id"),
|
|
296
|
+
"Root": field(session, "root_session_id"),
|
|
297
|
+
"Created": timefmt.absolute(field(session, "create_time")),
|
|
298
|
+
"Last activity": timefmt.absolute(field(session, "last_activity_time")),
|
|
299
|
+
},
|
|
300
|
+
status="ACTIVE",
|
|
301
|
+
snippets=_session_starter_code(obj, store, session_id) if store and session_id else None,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@sessions.command("create")
|
|
306
|
+
@click.option("--store", required=True)
|
|
307
|
+
@click.option("--actor-id", required=True, help="Application actor id (child must match parent).")
|
|
308
|
+
@click.option("--session-id", default=None, help="Optional caller-chosen id.")
|
|
309
|
+
@click.option("--parent-session-id", default=None)
|
|
310
|
+
@click.option("--metadata", default=None, help="JSON object of string labels.")
|
|
311
|
+
@click.pass_obj
|
|
312
|
+
def sessions_create(obj, store, actor_id, session_id, parent_session_id, metadata) -> None:
|
|
313
|
+
"""Create a session in a store."""
|
|
314
|
+
data = obj.client().create_session(
|
|
315
|
+
store, actor_id, session_id, parent_session_id, _parse_metadata(metadata)
|
|
316
|
+
)
|
|
317
|
+
if obj.output == "json":
|
|
318
|
+
render.emit_json(data)
|
|
319
|
+
return
|
|
320
|
+
render.success(
|
|
321
|
+
f"Created session '{field(data, 'session_id')}'",
|
|
322
|
+
fields={"Actor": actor_id, "Store": store},
|
|
323
|
+
next_steps=[
|
|
324
|
+
(
|
|
325
|
+
f"ab sessions items append --store {store} "
|
|
326
|
+
f"--session-id {field(data, 'session_id')} --data '{{...}}'",
|
|
327
|
+
"Append an item to this session",
|
|
328
|
+
),
|
|
329
|
+
],
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@sessions.command("list")
|
|
334
|
+
@click.option("--store", required=True)
|
|
335
|
+
@click.option("--filter", "filter_", default=None, help='e.g. actor_id = "support-123".')
|
|
336
|
+
@click.option("--order-by", default=None, help="e.g. 'last_activity_time desc'.")
|
|
337
|
+
@click.option("--page-size", type=int, default=None)
|
|
338
|
+
@click.option("--page-token", default=None)
|
|
339
|
+
@click.pass_obj
|
|
340
|
+
def sessions_list(obj, store, filter_, order_by, page_size, page_token) -> None:
|
|
341
|
+
"""List sessions in a store."""
|
|
342
|
+
data = obj.client().list_sessions(store, filter_, order_by, page_size, page_token)
|
|
343
|
+
if obj.output == "json":
|
|
344
|
+
render.emit_json(data)
|
|
345
|
+
return
|
|
346
|
+
items_ = field(data, "sessions") or []
|
|
347
|
+
rows = [
|
|
348
|
+
[
|
|
349
|
+
field(s, "session_id"),
|
|
350
|
+
field(s, "actor_id"),
|
|
351
|
+
field(s, "root_session_id"),
|
|
352
|
+
timefmt.relative(field(s, "create_time")),
|
|
353
|
+
timefmt.relative(field(s, "last_activity_time")),
|
|
354
|
+
]
|
|
355
|
+
for s in items_
|
|
356
|
+
]
|
|
357
|
+
render.resource_table(
|
|
358
|
+
f"Sessions · {store}",
|
|
359
|
+
[
|
|
360
|
+
("Session ID", "left"),
|
|
361
|
+
("Actor", "left"),
|
|
362
|
+
("Root", "left"),
|
|
363
|
+
("Created", "left"),
|
|
364
|
+
("Last Activity", "left"),
|
|
365
|
+
],
|
|
366
|
+
rows,
|
|
367
|
+
subtitle=_page_note(data),
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@sessions.command("get")
|
|
372
|
+
@click.argument("session_id")
|
|
373
|
+
@click.option("--store", default=None, help="Session store name (required in this preview).")
|
|
374
|
+
@click.pass_obj
|
|
375
|
+
def sessions_get(obj, session_id, store) -> None:
|
|
376
|
+
"""Get a session by id."""
|
|
377
|
+
if not store:
|
|
378
|
+
raise AgentCliError(
|
|
379
|
+
"Provide --store. Resolving a session by id alone is not supported in this preview."
|
|
380
|
+
)
|
|
381
|
+
data = obj.client().get_session(session_id, store)
|
|
382
|
+
if obj.output == "json":
|
|
383
|
+
render.emit_json(data)
|
|
384
|
+
return
|
|
385
|
+
_render_session_detail(obj, data, store)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@sessions.command("update")
|
|
389
|
+
@click.argument("session_id")
|
|
390
|
+
@click.option("--store", required=True)
|
|
391
|
+
@click.option(
|
|
392
|
+
"--metadata", required=True, help="JSON object of string labels (only mutable field)."
|
|
393
|
+
)
|
|
394
|
+
@click.pass_obj
|
|
395
|
+
def sessions_update(obj, session_id, store, metadata) -> None:
|
|
396
|
+
"""Update a session's metadata."""
|
|
397
|
+
data = obj.client().update_session(store, session_id, _parse_metadata(metadata) or {})
|
|
398
|
+
if obj.output == "json":
|
|
399
|
+
render.emit_json(data)
|
|
400
|
+
return
|
|
401
|
+
_render_session_detail(obj, data, store)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@sessions.command("delete")
|
|
405
|
+
@click.argument("session_id")
|
|
406
|
+
@click.option("--store", required=True)
|
|
407
|
+
@click.option("--force", is_flag=True, help="Cascade-delete descendant sessions.")
|
|
408
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip the confirmation prompt.")
|
|
409
|
+
@click.pass_obj
|
|
410
|
+
def sessions_delete(obj, session_id, store, force, yes) -> None:
|
|
411
|
+
"""Delete a session."""
|
|
412
|
+
render.confirm_destroy(f"session '{session_id}'", assume_yes=yes)
|
|
413
|
+
obj.client().delete_session(store, session_id, force)
|
|
414
|
+
if obj.output == "json":
|
|
415
|
+
render.emit_json({"deleted": session_id})
|
|
416
|
+
return
|
|
417
|
+
render.success(f"Deleted session '{session_id}'")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@sessions.command("fork")
|
|
421
|
+
@click.argument("source_session_id_arg", required=False, metavar="[SOURCE_SESSION_ID]")
|
|
422
|
+
@click.option("--store", required=True)
|
|
423
|
+
@click.option(
|
|
424
|
+
"--source-session-id",
|
|
425
|
+
"source_session_id_opt",
|
|
426
|
+
default=None,
|
|
427
|
+
help="Source session to fork (or pass it as the positional argument).",
|
|
428
|
+
)
|
|
429
|
+
@click.option("--actor-id", required=True)
|
|
430
|
+
@click.option("--up-to-item-id", default=None, help="Copy through this item id inclusively.")
|
|
431
|
+
@click.option("--session-id", default=None, help="Optional id for the fork.")
|
|
432
|
+
@click.option("--metadata", default=None)
|
|
433
|
+
@click.pass_obj
|
|
434
|
+
def sessions_fork(
|
|
435
|
+
obj,
|
|
436
|
+
source_session_id_arg,
|
|
437
|
+
store,
|
|
438
|
+
source_session_id_opt,
|
|
439
|
+
actor_id,
|
|
440
|
+
up_to_item_id,
|
|
441
|
+
session_id,
|
|
442
|
+
metadata,
|
|
443
|
+
) -> None:
|
|
444
|
+
"""Fork a session into a new independent top-level session."""
|
|
445
|
+
source_session_id = source_session_id_arg or source_session_id_opt
|
|
446
|
+
if not source_session_id:
|
|
447
|
+
raise AgentCliError(
|
|
448
|
+
"Provide the source session id (as the positional argument or --source-session-id)."
|
|
449
|
+
)
|
|
450
|
+
if source_session_id_arg and source_session_id_opt:
|
|
451
|
+
raise AgentCliError(
|
|
452
|
+
"Pass the source session id once — either positionally or via --source-session-id."
|
|
453
|
+
)
|
|
454
|
+
data = obj.client().fork_session(
|
|
455
|
+
store, source_session_id, actor_id, up_to_item_id, session_id, _parse_metadata(metadata)
|
|
456
|
+
)
|
|
457
|
+
if obj.output == "json":
|
|
458
|
+
render.emit_json(data)
|
|
459
|
+
return
|
|
460
|
+
_render_session_detail(obj, field(data, "session") or {}, store)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# --- session items ----------------------------------------------------------
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@items.command("list")
|
|
467
|
+
@click.option("--store", required=True)
|
|
468
|
+
@click.option("--session-id", required=True)
|
|
469
|
+
@click.option("--order-by", default=None, help="'create_time asc' or 'create_time desc'.")
|
|
470
|
+
@click.option("--page-size", type=int, default=None)
|
|
471
|
+
@click.option("--page-token", default=None)
|
|
472
|
+
@click.pass_obj
|
|
473
|
+
def items_list(obj, store, session_id, order_by, page_size, page_token) -> None:
|
|
474
|
+
"""List transcript items in a session."""
|
|
475
|
+
data = obj.client().list_session_items(store, session_id, order_by, page_size, page_token)
|
|
476
|
+
if obj.output == "json":
|
|
477
|
+
render.emit_json(data)
|
|
478
|
+
return
|
|
479
|
+
items_ = field(data, "session_items") or []
|
|
480
|
+
rows = [
|
|
481
|
+
[
|
|
482
|
+
field(it, "item_id"),
|
|
483
|
+
timefmt.relative(field(it, "create_time")),
|
|
484
|
+
_truncate(field(it, "data"), 70),
|
|
485
|
+
]
|
|
486
|
+
for it in items_
|
|
487
|
+
]
|
|
488
|
+
render.resource_table(
|
|
489
|
+
f"Session Items · {session_id}",
|
|
490
|
+
[("Item ID", "left"), ("Created", "left"), ("Data", "left")],
|
|
491
|
+
rows,
|
|
492
|
+
subtitle=_page_note(data),
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@items.command("append")
|
|
497
|
+
@click.option("--store", required=True)
|
|
498
|
+
@click.option("--session-id", required=True)
|
|
499
|
+
@click.option("--data", "data_", multiple=True, help="One item's JSON data (repeatable).")
|
|
500
|
+
@click.option(
|
|
501
|
+
"--file", "file_", type=click.File("r"), default=None, help="JSON array of item data values."
|
|
502
|
+
)
|
|
503
|
+
@click.pass_obj
|
|
504
|
+
def items_append(obj, store, session_id, data_, file_) -> None:
|
|
505
|
+
"""Append one or more items to a session (atomic, in order)."""
|
|
506
|
+
payload = _load_items(data_, file_)
|
|
507
|
+
result = obj.client().append_session_items(store, session_id, payload)
|
|
508
|
+
if obj.output == "json":
|
|
509
|
+
render.emit_json(result)
|
|
510
|
+
return
|
|
511
|
+
appended = field(result, "session_items") or []
|
|
512
|
+
render.success(f"Appended {len(appended)} item(s) to session '{session_id}'")
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@items.command("pop")
|
|
516
|
+
@click.option("--store", required=True)
|
|
517
|
+
@click.option("--session-id", required=True)
|
|
518
|
+
@click.pass_obj
|
|
519
|
+
def items_pop(obj, store, session_id) -> None:
|
|
520
|
+
"""Remove and return the most recent item."""
|
|
521
|
+
data = obj.client().pop_session_item(store, session_id)
|
|
522
|
+
if obj.output == "json":
|
|
523
|
+
render.emit_json(data)
|
|
524
|
+
return
|
|
525
|
+
item = field(data, "item")
|
|
526
|
+
if not item:
|
|
527
|
+
render.success("Session was already empty")
|
|
528
|
+
return
|
|
529
|
+
render.success(
|
|
530
|
+
"Popped last item",
|
|
531
|
+
fields={
|
|
532
|
+
"Item ID": field(item, "item_id"),
|
|
533
|
+
"Data": _truncate(field(item, "data"), 70),
|
|
534
|
+
},
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@items.command("clear")
|
|
539
|
+
@click.option("--store", required=True)
|
|
540
|
+
@click.option("--session-id", required=True)
|
|
541
|
+
@click.pass_obj
|
|
542
|
+
def items_clear(obj, store, session_id) -> None:
|
|
543
|
+
"""Remove all items from a session."""
|
|
544
|
+
obj.client().clear_session_items(store, session_id)
|
|
545
|
+
if obj.output == "json":
|
|
546
|
+
render.emit_json({"cleared": session_id})
|
|
547
|
+
return
|
|
548
|
+
render.success(f"Cleared items from session '{session_id}'")
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
# --- shared helpers ---------------------------------------------------------
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def _parse_metadata(value: Optional[str]) -> Optional[dict]:
|
|
555
|
+
if value is None:
|
|
556
|
+
return None
|
|
557
|
+
try:
|
|
558
|
+
parsed = json.loads(value)
|
|
559
|
+
except json.JSONDecodeError as exc:
|
|
560
|
+
raise AgentCliError(f"--metadata must be valid JSON: {exc}") from exc
|
|
561
|
+
if not isinstance(parsed, dict):
|
|
562
|
+
raise AgentCliError("--metadata must be a JSON object of string labels.")
|
|
563
|
+
return parsed
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _load_items(data_: tuple[str, ...], file_) -> list[Any]:
|
|
567
|
+
payload: list[Any] = []
|
|
568
|
+
if file_ is not None:
|
|
569
|
+
try:
|
|
570
|
+
loaded = json.load(file_)
|
|
571
|
+
except json.JSONDecodeError as exc:
|
|
572
|
+
raise AgentCliError(f"--file must contain a JSON array: {exc}") from exc
|
|
573
|
+
if not isinstance(loaded, list):
|
|
574
|
+
raise AgentCliError("--file must contain a JSON array of item data values.")
|
|
575
|
+
payload.extend(loaded)
|
|
576
|
+
for raw in data_:
|
|
577
|
+
try:
|
|
578
|
+
payload.append(json.loads(raw))
|
|
579
|
+
except json.JSONDecodeError as exc:
|
|
580
|
+
raise AgentCliError(f"--data must be valid JSON: {exc}") from exc
|
|
581
|
+
if not payload:
|
|
582
|
+
raise AgentCliError("Provide at least one --data item or a --file.")
|
|
583
|
+
return payload
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _page_note(data: dict) -> str | None:
|
|
587
|
+
token = field(data, "next_page_token")
|
|
588
|
+
return f"More results available — pass --page-token {token}" if token else None
|