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,496 @@
|
|
|
1
|
+
"""`ab init` — scaffold a local agent project from an Agent Bricks template.
|
|
2
|
+
|
|
3
|
+
Copies one template bundled in the databricks_agentbricks package into a local target directory, ready
|
|
4
|
+
for `ab deploy --source <dir>`. Because the template ships with the package, the scaffold always
|
|
5
|
+
matches the installed CLI; to try a fork or branch, install that agentbricks and re-run init.
|
|
6
|
+
|
|
7
|
+
The Agent Bricks server provisions its Runtime Store at deployment. Use `--server custom` for a minimal
|
|
8
|
+
foreground-only FastAPI server.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import pathlib
|
|
15
|
+
import secrets
|
|
16
|
+
import shlex
|
|
17
|
+
import shutil
|
|
18
|
+
import string
|
|
19
|
+
import tempfile
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from importlib import resources
|
|
22
|
+
from importlib.metadata import PackageNotFoundError
|
|
23
|
+
from importlib.metadata import version as _installed_version
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
import click
|
|
27
|
+
|
|
28
|
+
from databricks_agentbricks import render
|
|
29
|
+
from databricks_agentbricks.agent_project import AgentProject, default_store_name
|
|
30
|
+
from databricks_agentbricks.cli.tracing import default_experiment_name
|
|
31
|
+
from databricks_agentbricks.errors import AgentCliError
|
|
32
|
+
from databricks_agentbricks.project_config import write_project_metadata
|
|
33
|
+
from databricks_agentbricks.project_types import (
|
|
34
|
+
AgentFramework,
|
|
35
|
+
AgentServer,
|
|
36
|
+
parse_framework,
|
|
37
|
+
parse_server,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Templates ship inside this package (databricks_agentbricks/templates/), so `ab init` always copies
|
|
41
|
+
# the one for the installed CLI — the scaffold can't drift from the databricks-agentbricks it runs
|
|
42
|
+
# against. For an editable install `resources.files` resolves to the source tree, so an Agent Bricks
|
|
43
|
+
# developer's uncommitted template edits are scaffolded too.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class _AgentTemplate:
|
|
48
|
+
"""The bundled templates for one framework (directories under databricks_agentbricks/templates/)."""
|
|
49
|
+
|
|
50
|
+
agentbricks_server: str # scaffold for the Agent Bricks invocation server (the default)
|
|
51
|
+
custom_server: str # scaffold for the minimal custom FastAPI server (--server custom)
|
|
52
|
+
chat_app: str # browser chat-app template, overlaid on the Agent Bricks server scaffold
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Framework -> its bundled templates.
|
|
56
|
+
_TEMPLATES = {
|
|
57
|
+
AgentFramework.LANGGRAPH: _AgentTemplate(
|
|
58
|
+
"agent-langgraph", "custom-agent-langgraph", "ui/agent-langgraph"
|
|
59
|
+
),
|
|
60
|
+
AgentFramework.OPENAI: _AgentTemplate("agent-openai", "custom-agent-openai", "ui/agent-openai"),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Framework -> display label for user-facing text (prompts, help).
|
|
64
|
+
_FRAMEWORK_LABELS = {
|
|
65
|
+
AgentFramework.LANGGRAPH: "LangGraph",
|
|
66
|
+
AgentFramework.OPENAI: "OpenAI Agents SDK",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _or_join(items: list[str]) -> str:
|
|
71
|
+
"""Join items into a human-readable phrase: "a", "a or b", or "a, b, or c"."""
|
|
72
|
+
if len(items) <= 1:
|
|
73
|
+
return items[0] if items else ""
|
|
74
|
+
if len(items) == 2:
|
|
75
|
+
return f"{items[0]} or {items[1]}"
|
|
76
|
+
return ", ".join(items[:-1]) + f", or {items[-1]}"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Derived from _FRAMEWORK_LABELS / AgentFramework so user-facing text never drifts from the
|
|
80
|
+
# supported-framework list. NOTE: these must stay above the click decorators below — Click
|
|
81
|
+
# evaluates `help=` strings at import time, not at call time.
|
|
82
|
+
_FRAMEWORK_LABEL_PHRASE = _or_join(list(_FRAMEWORK_LABELS.values()))
|
|
83
|
+
_FRAMEWORK_VALUE_PHRASE = ", ".join(framework.value for framework in AgentFramework)
|
|
84
|
+
|
|
85
|
+
_MIGRATION_DIR = "agent-bricks-migrate"
|
|
86
|
+
# Each coding agent discovers skills in its own configuration directory, so the bundle lives in one
|
|
87
|
+
# tool-neutral directory and every agent gets a pointer to it rather than a copy of the reference.
|
|
88
|
+
_POINTER_ROOTS = (".claude", ".agent")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _copy_packaged_template(
|
|
92
|
+
name: str,
|
|
93
|
+
dest: pathlib.Path,
|
|
94
|
+
overlay_names: tuple[str, ...] = (),
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Copy a template (and any overlays) bundled in the databricks_agentbricks package into `dest`.
|
|
97
|
+
|
|
98
|
+
The templates ship with the package, so a scaffold always matches the installed CLI. For an
|
|
99
|
+
editable install `resources.files` resolves to the source tree, so an Agent Bricks developer's
|
|
100
|
+
uncommitted template edits are scaffolded too — no git fetch, no version matching.
|
|
101
|
+
"""
|
|
102
|
+
root = resources.files("databricks_agentbricks").joinpath("templates")
|
|
103
|
+
for index, rel in enumerate((name, *overlay_names)):
|
|
104
|
+
src = root.joinpath(*rel.split("/"))
|
|
105
|
+
if not src.is_dir():
|
|
106
|
+
raise AgentCliError(f"Template '{rel}' is not bundled in databricks-agentbricks.")
|
|
107
|
+
shutil.copytree(
|
|
108
|
+
str(src), dest, dirs_exist_ok=index > 0, ignore=shutil.ignore_patterns("__pycache__")
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _bundled_template_ref() -> str:
|
|
113
|
+
"""A label for the packaged template's origin — the installed databricks-agentbricks version."""
|
|
114
|
+
try:
|
|
115
|
+
return f"bundled (databricks-agentbricks {_installed_version('databricks-agentbricks')})"
|
|
116
|
+
except PackageNotFoundError:
|
|
117
|
+
return "bundled"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _write_env(dest: pathlib.Path, profile: str) -> bool:
|
|
121
|
+
"""Seed a local `.env` from `.env.example` with DATABRICKS_CONFIG_PROFILE=<profile>.
|
|
122
|
+
|
|
123
|
+
Returns True if a `.env` was written. Skips if `.env` already exists (never clobbers). The
|
|
124
|
+
template reads DATABRICKS_CONFIG_PROFILE for local model auth, so this makes the scaffolded
|
|
125
|
+
project runnable with `ab dev` without a manual `cp .env.example .env` step.
|
|
126
|
+
"""
|
|
127
|
+
env_path = dest / ".env"
|
|
128
|
+
if env_path.exists():
|
|
129
|
+
return False
|
|
130
|
+
example = dest / ".env.example"
|
|
131
|
+
base = example.read_text() if example.exists() else ""
|
|
132
|
+
lines, replaced = [], False
|
|
133
|
+
for line in base.splitlines():
|
|
134
|
+
if line.startswith("DATABRICKS_CONFIG_PROFILE="):
|
|
135
|
+
lines.append(f"DATABRICKS_CONFIG_PROFILE={profile}")
|
|
136
|
+
replaced = True
|
|
137
|
+
else:
|
|
138
|
+
lines.append(line)
|
|
139
|
+
if not replaced:
|
|
140
|
+
lines.insert(0, f"DATABRICKS_CONFIG_PROFILE={profile}")
|
|
141
|
+
env_path.write_text("\n".join(lines) + "\n")
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _migration_paths(dest: pathlib.Path, target: pathlib.Path) -> tuple[pathlib.Path, ...]:
|
|
146
|
+
"""`target` plus every directory under `dest` that Agent Bricks would have to create to reach it."""
|
|
147
|
+
parts = target.relative_to(dest).parts
|
|
148
|
+
return tuple(dest.joinpath(*parts[:index]) for index in range(1, len(parts) + 1))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _pointer_skill(skill: pathlib.Path) -> str:
|
|
152
|
+
"""A skill file forwarding an agent to the one bundle, so the reference is never duplicated."""
|
|
153
|
+
_, frontmatter, _ = skill.read_text(encoding="utf-8").split("---", 2)
|
|
154
|
+
target = f"../../../{_MIGRATION_DIR}/SKILL.md"
|
|
155
|
+
return (
|
|
156
|
+
f"---{frontmatter}---\n\n"
|
|
157
|
+
f"The migration bundle lives at [{target}]({target}), outside any single agent's "
|
|
158
|
+
"configuration directory.\n\nRead that file and follow it. Its `references/` directory "
|
|
159
|
+
"holds the migration settings and the candidate project generated from the templates "
|
|
160
|
+
"bundled with the installed CLI.\n"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _install_migration(
|
|
165
|
+
staged: pathlib.Path, bundle: pathlib.Path, pointers: tuple[pathlib.Path, ...]
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Install the staged bundle, removing anything created here if a later write fails."""
|
|
168
|
+
pointer_skill = _pointer_skill(staged / "SKILL.md")
|
|
169
|
+
created: list[pathlib.Path] = []
|
|
170
|
+
try:
|
|
171
|
+
shutil.copytree(staged, bundle)
|
|
172
|
+
created.append(bundle)
|
|
173
|
+
for pointer in pointers:
|
|
174
|
+
# Remember the outermost directory Agent Bricks creates so cleanup never removes a
|
|
175
|
+
# pre-existing agent configuration directory.
|
|
176
|
+
outermost = pointer
|
|
177
|
+
while not outermost.parent.exists():
|
|
178
|
+
outermost = outermost.parent
|
|
179
|
+
pointer.mkdir(parents=True)
|
|
180
|
+
created.append(outermost)
|
|
181
|
+
(pointer / "SKILL.md").write_text(pointer_skill, encoding="utf-8")
|
|
182
|
+
except Exception:
|
|
183
|
+
for path in created:
|
|
184
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
185
|
+
raise
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _prepare_migration(
|
|
189
|
+
obj,
|
|
190
|
+
dest: pathlib.Path,
|
|
191
|
+
*,
|
|
192
|
+
framework: AgentFramework,
|
|
193
|
+
chat_app_enabled: bool,
|
|
194
|
+
profile: Optional[str],
|
|
195
|
+
memory_store: Optional[str],
|
|
196
|
+
session_store: Optional[str],
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Prepare a reference project and migration instructions without changing the application."""
|
|
199
|
+
if not dest.is_dir():
|
|
200
|
+
raise AgentCliError(f"Existing project directory '{dest}' was not found.")
|
|
201
|
+
bundle = dest / _MIGRATION_DIR
|
|
202
|
+
pointers = tuple(dest / root / "skills" / _MIGRATION_DIR for root in _POINTER_ROOTS)
|
|
203
|
+
for target in (bundle, *pointers):
|
|
204
|
+
for path in _migration_paths(dest, target):
|
|
205
|
+
if path.is_symlink() or (path.exists() and not path.is_dir()):
|
|
206
|
+
raise AgentCliError(f"Cannot write migration files at '{path}'.")
|
|
207
|
+
if target.exists():
|
|
208
|
+
relative_target = target.relative_to(dest).as_posix()
|
|
209
|
+
if target == bundle:
|
|
210
|
+
hint = (
|
|
211
|
+
f"Use the existing {relative_target}/PROMPT.md, or move that directory "
|
|
212
|
+
"before regenerating."
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
hint = f"Move the existing skill at {relative_target} before regenerating."
|
|
216
|
+
raise AgentCliError(
|
|
217
|
+
f"Migration files already exist at '{target}'.",
|
|
218
|
+
hint=hint,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Build the bundle before touching the project, so a failed copy leaves no partial skill.
|
|
222
|
+
with tempfile.TemporaryDirectory(prefix="agent-bricks-migrate-") as tmp:
|
|
223
|
+
staged = pathlib.Path(tmp) / _MIGRATION_DIR
|
|
224
|
+
reference = staged / "references" / "template"
|
|
225
|
+
template = _TEMPLATES[framework]
|
|
226
|
+
overlays = (template.chat_app,) if chat_app_enabled else ()
|
|
227
|
+
_copy_packaged_template(template.agentbricks_server, reference, overlays)
|
|
228
|
+
project_name = dest.resolve().name
|
|
229
|
+
token = "".join(secrets.choice(string.ascii_lowercase) for _ in range(6))
|
|
230
|
+
AgentProject.create(
|
|
231
|
+
reference,
|
|
232
|
+
framework=framework,
|
|
233
|
+
server=AgentServer.AGENTBRICKS,
|
|
234
|
+
memory_store=memory_store or default_store_name(project_name, "memory", token),
|
|
235
|
+
session_store=session_store or default_store_name(project_name, "sessions", token),
|
|
236
|
+
).write()
|
|
237
|
+
write_project_metadata(reference, framework=framework, template=template.agentbricks_server)
|
|
238
|
+
source = (
|
|
239
|
+
resources.files("databricks_agentbricks").joinpath("templates").joinpath(_MIGRATION_DIR)
|
|
240
|
+
)
|
|
241
|
+
(staged / "SKILL.md").write_text(
|
|
242
|
+
source.joinpath("SKILL.md").read_text(encoding="utf-8"), encoding="utf-8"
|
|
243
|
+
)
|
|
244
|
+
template_ref = _bundled_template_ref()
|
|
245
|
+
(staged / "references" / "migration.json").write_text(
|
|
246
|
+
json.dumps(
|
|
247
|
+
{
|
|
248
|
+
"framework": framework.value,
|
|
249
|
+
"template_ref": template_ref,
|
|
250
|
+
"server": "agentbricks",
|
|
251
|
+
"chat_app_enabled": chat_app_enabled,
|
|
252
|
+
"profile": profile,
|
|
253
|
+
},
|
|
254
|
+
indent=2,
|
|
255
|
+
)
|
|
256
|
+
+ "\n",
|
|
257
|
+
encoding="utf-8",
|
|
258
|
+
)
|
|
259
|
+
prompt = (
|
|
260
|
+
f"Use the {_MIGRATION_DIR} skill at {_MIGRATION_DIR}/SKILL.md to adapt "
|
|
261
|
+
f"my existing {_FRAMEWORK_LABELS[framework]} agent in this project for Agent Bricks. Read "
|
|
262
|
+
"its migration settings and local template reference. Implement and verify the "
|
|
263
|
+
"integration while preserving my agent's behavior. Explicitly handle existing "
|
|
264
|
+
"persistence, conversation history, custom state, and client contracts; surface any "
|
|
265
|
+
"unresolved migration choices. Report which Agent Bricks commands are ready and any "
|
|
266
|
+
"remaining limitations.\n"
|
|
267
|
+
)
|
|
268
|
+
(staged / "PROMPT.md").write_text(prompt, encoding="utf-8")
|
|
269
|
+
_install_migration(staged, bundle, pointers)
|
|
270
|
+
|
|
271
|
+
if obj.output == "json":
|
|
272
|
+
render.emit_json(
|
|
273
|
+
{
|
|
274
|
+
"mode": "existing",
|
|
275
|
+
"framework": framework.value,
|
|
276
|
+
"directory": str(dest),
|
|
277
|
+
"bundle": str(bundle),
|
|
278
|
+
"skill": str(bundle / "SKILL.md"),
|
|
279
|
+
"prompt_file": str(bundle / "PROMPT.md"),
|
|
280
|
+
"prompt": prompt.strip(),
|
|
281
|
+
"pointers": [str(pointer / "SKILL.md") for pointer in pointers],
|
|
282
|
+
"template_ref": template_ref,
|
|
283
|
+
"chat_app_enabled": chat_app_enabled,
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
return
|
|
287
|
+
render.success(
|
|
288
|
+
"Prepared migration instructions (agent conversion is still required)",
|
|
289
|
+
fields={"Directory": str(dest), "Skill": str(bundle / "SKILL.md")},
|
|
290
|
+
next_steps=[
|
|
291
|
+
(f"cd {shlex.quote(str(dest))}", "Enter the existing project"),
|
|
292
|
+
f"Open your coding agent and paste the prompt from {_MIGRATION_DIR}/PROMPT.md:",
|
|
293
|
+
prompt.strip(),
|
|
294
|
+
],
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@click.command(name="init")
|
|
299
|
+
@click.argument("directory", required=False)
|
|
300
|
+
@click.option(
|
|
301
|
+
"--existing",
|
|
302
|
+
is_flag=True,
|
|
303
|
+
help=f"Prepare a coding-agent migration bundle for an existing {_FRAMEWORK_LABEL_PHRASE} "
|
|
304
|
+
"project (defaults to .).",
|
|
305
|
+
)
|
|
306
|
+
@click.option(
|
|
307
|
+
"--framework",
|
|
308
|
+
type=click.Choice([framework.value for framework in AgentFramework]),
|
|
309
|
+
default=None,
|
|
310
|
+
help="Agent framework to scaffold (defaults to langgraph).",
|
|
311
|
+
)
|
|
312
|
+
@click.option(
|
|
313
|
+
"--server",
|
|
314
|
+
type=click.Choice([server.value for server in AgentServer]),
|
|
315
|
+
default=AgentServer.AGENTBRICKS.value,
|
|
316
|
+
show_default=True,
|
|
317
|
+
help="Use the Agent Bricks invocation server or a minimal custom FastAPI server.",
|
|
318
|
+
)
|
|
319
|
+
@click.option(
|
|
320
|
+
"--profile",
|
|
321
|
+
default=None,
|
|
322
|
+
help="Seed a local .env with this DATABRICKS_CONFIG_PROFILE so `ab dev` works "
|
|
323
|
+
"immediately (defaults to the profile from -p / `ab login`).",
|
|
324
|
+
)
|
|
325
|
+
@click.option(
|
|
326
|
+
"--disable-chat-app",
|
|
327
|
+
is_flag=True,
|
|
328
|
+
help="Scaffold the API-only backend, without the browser chat app.",
|
|
329
|
+
)
|
|
330
|
+
@click.option(
|
|
331
|
+
"--enable-chat-app",
|
|
332
|
+
is_flag=True,
|
|
333
|
+
hidden=True,
|
|
334
|
+
help="Deprecated: the chat app is included by default; this flag is a no-op.",
|
|
335
|
+
)
|
|
336
|
+
@click.option(
|
|
337
|
+
"--memory-store",
|
|
338
|
+
"memory_store",
|
|
339
|
+
default=None,
|
|
340
|
+
help="Name for the declared memory store (default: a unique name derived from the directory, "
|
|
341
|
+
"<dir>-<token>-memory). Only --server agentbricks declares stores by default.",
|
|
342
|
+
)
|
|
343
|
+
@click.option(
|
|
344
|
+
"--session-store",
|
|
345
|
+
"session_store",
|
|
346
|
+
default=None,
|
|
347
|
+
help="Name for the declared session store (default: a unique name derived from the directory, "
|
|
348
|
+
"<dir>-<token>-sessions).",
|
|
349
|
+
)
|
|
350
|
+
@click.pass_obj
|
|
351
|
+
def init(
|
|
352
|
+
obj,
|
|
353
|
+
directory: Optional[str],
|
|
354
|
+
existing: bool,
|
|
355
|
+
framework: Optional[str],
|
|
356
|
+
server: str,
|
|
357
|
+
profile: Optional[str],
|
|
358
|
+
disable_chat_app: bool,
|
|
359
|
+
enable_chat_app: bool,
|
|
360
|
+
memory_store: Optional[str],
|
|
361
|
+
session_store: Optional[str],
|
|
362
|
+
) -> None:
|
|
363
|
+
"""Scaffold a local agent project from an Agent Bricks template.
|
|
364
|
+
|
|
365
|
+
DIRECTORY is the target path to create (defaults to the template's own name). The
|
|
366
|
+
directory must not already exist unless --existing is supplied. Once scaffolded, deploy it with
|
|
367
|
+
`ab deploy <name> --source <directory>`.
|
|
368
|
+
|
|
369
|
+
Pass --profile (or set a default via `ab login` / -p) to seed a local `.env` so the
|
|
370
|
+
scaffolded project runs with `ab dev` right away.
|
|
371
|
+
|
|
372
|
+
The scaffold is preconfigured to call Databricks model serving through the AI Gateway using
|
|
373
|
+
that profile, so it can talk to a model with no separate endpoint or API key to set up.
|
|
374
|
+
|
|
375
|
+
The default Agent Bricks server supports foreground, streaming, and background invocations through one
|
|
376
|
+
HTTP contract and Runtime Store. Pass --server custom for a minimal foreground-only
|
|
377
|
+
FastAPI server.
|
|
378
|
+
|
|
379
|
+
With --existing, prepare a skill, prompt, and bundled template reference under
|
|
380
|
+
agent-bricks-migrate/, and point each supported coding agent's skills directory at it. Run the
|
|
381
|
+
prompt in your coding agent to migrate the agent onto Agent Bricks; init leaves existing
|
|
382
|
+
application source, dependencies, and configuration intact.
|
|
383
|
+
"""
|
|
384
|
+
selected_framework = parse_framework(framework or AgentFramework.LANGGRAPH)
|
|
385
|
+
selected_server = parse_server(server)
|
|
386
|
+
agentbricks_server = selected_server == AgentServer.AGENTBRICKS
|
|
387
|
+
template = _TEMPLATES[selected_framework]
|
|
388
|
+
template_name = template.agentbricks_server if agentbricks_server else template.custom_server
|
|
389
|
+
chat_app_enabled = agentbricks_server and not disable_chat_app
|
|
390
|
+
if existing:
|
|
391
|
+
if not agentbricks_server:
|
|
392
|
+
raise click.UsageError(
|
|
393
|
+
"--existing requires --server agentbricks (supported frameworks: "
|
|
394
|
+
f"{_FRAMEWORK_VALUE_PHRASE})."
|
|
395
|
+
)
|
|
396
|
+
_prepare_migration(
|
|
397
|
+
obj,
|
|
398
|
+
pathlib.Path(directory or "."),
|
|
399
|
+
framework=selected_framework,
|
|
400
|
+
chat_app_enabled=chat_app_enabled,
|
|
401
|
+
profile=profile or obj.profile,
|
|
402
|
+
memory_store=memory_store,
|
|
403
|
+
session_store=session_store,
|
|
404
|
+
)
|
|
405
|
+
return
|
|
406
|
+
dest = pathlib.Path(directory) if directory else pathlib.Path(template_name)
|
|
407
|
+
|
|
408
|
+
if dest.exists():
|
|
409
|
+
raise AgentCliError(
|
|
410
|
+
f"Destination '{dest}' already exists.",
|
|
411
|
+
hint=f"Use --existing to prepare a migration from an existing "
|
|
412
|
+
f"{_FRAMEWORK_LABEL_PHRASE} project, or choose a new directory to scaffold.",
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
overlay_names = (template.chat_app,) if chat_app_enabled else ()
|
|
416
|
+
try:
|
|
417
|
+
# Copy the template bundled with the installed CLI. The scaffold keeps the template's own
|
|
418
|
+
# databricks-agentbricks PyPI dependency; it can't drift from the CLI because both ship together.
|
|
419
|
+
_copy_packaged_template(template_name, dest, overlay_names)
|
|
420
|
+
template_ref = _bundled_template_ref()
|
|
421
|
+
write_project_metadata(
|
|
422
|
+
dest,
|
|
423
|
+
framework=selected_framework,
|
|
424
|
+
template=template_name,
|
|
425
|
+
)
|
|
426
|
+
experiment_name = None
|
|
427
|
+
if agentbricks_server:
|
|
428
|
+
# Store display names aren't unique and projects often share a directory name, so a
|
|
429
|
+
# per-scaffold token keeps default stores from colliding. Memory, session, and the default
|
|
430
|
+
# tracing experiment share the token; an explicit --memory/session-store wins.
|
|
431
|
+
token = "".join(secrets.choice(string.ascii_lowercase) for _ in range(6))
|
|
432
|
+
memory_store = memory_store or default_store_name(dest.name, "memory", token)
|
|
433
|
+
session_store = session_store or default_store_name(dest.name, "sessions", token)
|
|
434
|
+
experiment_name = default_experiment_name(dest.name, token)
|
|
435
|
+
project = AgentProject.create(
|
|
436
|
+
dest,
|
|
437
|
+
framework=selected_framework,
|
|
438
|
+
server=selected_server,
|
|
439
|
+
memory_store=memory_store,
|
|
440
|
+
session_store=session_store,
|
|
441
|
+
experiment_name=experiment_name,
|
|
442
|
+
)
|
|
443
|
+
project.write()
|
|
444
|
+
env_profile = profile or obj.profile
|
|
445
|
+
wrote_env = _write_env(dest, env_profile) if env_profile else False
|
|
446
|
+
except Exception:
|
|
447
|
+
shutil.rmtree(dest, ignore_errors=True)
|
|
448
|
+
raise
|
|
449
|
+
|
|
450
|
+
if obj.output == "json":
|
|
451
|
+
render.emit_json(
|
|
452
|
+
{
|
|
453
|
+
"framework": selected_framework.value,
|
|
454
|
+
"template": template_name,
|
|
455
|
+
"template_ref": template_ref,
|
|
456
|
+
"directory": str(dest),
|
|
457
|
+
"server": selected_server.value,
|
|
458
|
+
"chat_app_enabled": chat_app_enabled,
|
|
459
|
+
"env_profile": env_profile if wrote_env else None,
|
|
460
|
+
"memory_store": memory_store,
|
|
461
|
+
"session_store": session_store,
|
|
462
|
+
"experiment_name": experiment_name,
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
return
|
|
466
|
+
|
|
467
|
+
fields = {
|
|
468
|
+
"Framework": selected_framework.value,
|
|
469
|
+
"Server": "Agent Bricks DurableAgentServer" if agentbricks_server else "Custom FastAPI",
|
|
470
|
+
"Template ref": template_ref,
|
|
471
|
+
"Directory": str(dest),
|
|
472
|
+
}
|
|
473
|
+
if chat_app_enabled:
|
|
474
|
+
fields["Chat app"] = "enabled"
|
|
475
|
+
# Surface the store names: with the random token they can't be inferred from the directory.
|
|
476
|
+
if memory_store:
|
|
477
|
+
fields["Memory store"] = memory_store
|
|
478
|
+
if session_store:
|
|
479
|
+
fields["Session store"] = session_store
|
|
480
|
+
if experiment_name:
|
|
481
|
+
fields["Traces experiment"] = experiment_name
|
|
482
|
+
steps: list[str | tuple[str, str]] = [(f"cd {dest}", "Enter the project directory")]
|
|
483
|
+
if wrote_env:
|
|
484
|
+
fields["Profile (.env)"] = env_profile
|
|
485
|
+
else:
|
|
486
|
+
# No profile resolved, so no .env was seeded — call out the auth step explicitly rather
|
|
487
|
+
# than burying it, since running locally fails without a Databricks profile.
|
|
488
|
+
steps += [
|
|
489
|
+
("cp .env.example .env", "Create your local env file"),
|
|
490
|
+
"Set DATABRICKS_CONFIG_PROFILE in .env (or re-run `ab init --profile <profile>`)",
|
|
491
|
+
]
|
|
492
|
+
steps.append(("ab dev", "Run the agent locally"))
|
|
493
|
+
if chat_app_enabled:
|
|
494
|
+
steps.append("Open http://localhost:8000 to chat with it")
|
|
495
|
+
steps.append((f"ab deploy {dest.name}", "Deploy it to Databricks (from the project dir)"))
|
|
496
|
+
render.success(f"Scaffolded '{template_name}'", fields=fields, next_steps=steps)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Discover Unity Catalog MCP Services that can be added to an agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from databricks_agentbricks.agent_project import _three_part_name
|
|
8
|
+
from databricks_agentbricks.errors import AgentCliError
|
|
9
|
+
|
|
10
|
+
_RESOURCE_PREFIX = "mcp-services/"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _validate_schema(schema: str) -> str:
|
|
14
|
+
parts = schema.strip().split(".")
|
|
15
|
+
if (
|
|
16
|
+
len(parts) != 2
|
|
17
|
+
or any(not part for part in parts)
|
|
18
|
+
or any(character.isspace() for character in schema)
|
|
19
|
+
):
|
|
20
|
+
raise AgentCliError(
|
|
21
|
+
f"Invalid schema {schema!r}.",
|
|
22
|
+
hint="Use a two-part Unity Catalog schema name: catalog.schema.",
|
|
23
|
+
)
|
|
24
|
+
return schema.strip()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _service_record(service: Any) -> dict[str, str] | None:
|
|
28
|
+
if not isinstance(service, dict):
|
|
29
|
+
return None
|
|
30
|
+
raw_name = service.get("name")
|
|
31
|
+
if not isinstance(raw_name, str) or not raw_name:
|
|
32
|
+
return None
|
|
33
|
+
name = raw_name.removeprefix(_RESOURCE_PREFIX)
|
|
34
|
+
record = {"name": name}
|
|
35
|
+
for field in ("id", "comment"):
|
|
36
|
+
value = service.get(field)
|
|
37
|
+
if isinstance(value, str) and value:
|
|
38
|
+
record[field] = value
|
|
39
|
+
return record
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _list_services(client: Any, schema: str) -> list[dict[str, str]]:
|
|
43
|
+
by_name: dict[str, dict[str, str]] = {}
|
|
44
|
+
seen_tokens: set[str] = set()
|
|
45
|
+
page_token = None
|
|
46
|
+
while True:
|
|
47
|
+
response = client.list_mcp_services(schema, page_token=page_token)
|
|
48
|
+
if not isinstance(response, dict):
|
|
49
|
+
raise AgentCliError("The MCP Services API returned an invalid response.")
|
|
50
|
+
services = response.get("mcp_services", [])
|
|
51
|
+
if not isinstance(services, list):
|
|
52
|
+
raise AgentCliError("The MCP Services API returned an invalid response.")
|
|
53
|
+
for service in services:
|
|
54
|
+
record = _service_record(service)
|
|
55
|
+
if record is None:
|
|
56
|
+
raise AgentCliError("The MCP Services API returned a service without a valid name.")
|
|
57
|
+
if record is not None:
|
|
58
|
+
_three_part_name(record["name"], "MCP service")
|
|
59
|
+
if record is not None and record["name"] not in by_name:
|
|
60
|
+
by_name[record["name"]] = record
|
|
61
|
+
page_token = response.get("next_page_token")
|
|
62
|
+
if page_token is not None and not isinstance(page_token, str):
|
|
63
|
+
raise AgentCliError("The MCP Services API returned an invalid pagination token.")
|
|
64
|
+
if not isinstance(page_token, str) or not page_token:
|
|
65
|
+
break
|
|
66
|
+
if page_token in seen_tokens:
|
|
67
|
+
raise AgentCliError("The MCP Services API repeated a pagination token.")
|
|
68
|
+
seen_tokens.add(page_token)
|
|
69
|
+
return [by_name[name] for name in sorted(by_name)]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _add_command(service: str) -> str:
|
|
73
|
+
if service == "system.ai.sandbox":
|
|
74
|
+
return "ab tools add sandbox --scope table:catalog.schema.table"
|
|
75
|
+
return f"ab tools add mcp {service}"
|