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.
Files changed (159) hide show
  1. databricks_agentbricks/__init__.py +1 -0
  2. databricks_agentbricks/_group.py +217 -0
  3. databricks_agentbricks/agent_project.py +657 -0
  4. databricks_agentbricks/app_resources.py +139 -0
  5. databricks_agentbricks/cli/__init__.py +12 -0
  6. databricks_agentbricks/cli/app.py +108 -0
  7. databricks_agentbricks/cli/app_auth.py +198 -0
  8. databricks_agentbricks/cli/auth.py +142 -0
  9. databricks_agentbricks/cli/deploy.py +954 -0
  10. databricks_agentbricks/cli/dev.py +310 -0
  11. databricks_agentbricks/cli/endpoint.py +145 -0
  12. databricks_agentbricks/cli/endpoint_examples.py +33 -0
  13. databricks_agentbricks/cli/endpoint_output.py +53 -0
  14. databricks_agentbricks/cli/endpoint_request.py +71 -0
  15. databricks_agentbricks/cli/endpoint_transport.py +148 -0
  16. databricks_agentbricks/cli/help.py +467 -0
  17. databricks_agentbricks/cli/init.py +496 -0
  18. databricks_agentbricks/cli/mcp.py +75 -0
  19. databricks_agentbricks/cli/memory.py +520 -0
  20. databricks_agentbricks/cli/sandbox.py +823 -0
  21. databricks_agentbricks/cli/sessions.py +588 -0
  22. databricks_agentbricks/cli/tools.py +456 -0
  23. databricks_agentbricks/cli/tracing.py +724 -0
  24. databricks_agentbricks/databricks_cli.py +38 -0
  25. databricks_agentbricks/errors.py +114 -0
  26. databricks_agentbricks/lakebase_runtime_store.py +107 -0
  27. databricks_agentbricks/legacy_lakebase_runtime_store.py +74 -0
  28. databricks_agentbricks/project_config.py +157 -0
  29. databricks_agentbricks/project_types.py +55 -0
  30. databricks_agentbricks/py.typed +0 -0
  31. databricks_agentbricks/render.py +349 -0
  32. databricks_agentbricks/templates/agent-bricks-migrate/SKILL.md +101 -0
  33. databricks_agentbricks/templates/agent-langgraph/.env.example +36 -0
  34. databricks_agentbricks/templates/agent-langgraph/.gitignore +199 -0
  35. databricks_agentbricks/templates/agent-langgraph/AGENTKIT_CONTRACT.md +95 -0
  36. databricks_agentbricks/templates/agent-langgraph/AGENTS.md +31 -0
  37. databricks_agentbricks/templates/agent-langgraph/CLAUDE.md +1 -0
  38. databricks_agentbricks/templates/agent-langgraph/README.md +141 -0
  39. databricks_agentbricks/templates/agent-langgraph/agent/__init__.py +0 -0
  40. databricks_agentbricks/templates/agent-langgraph/agent/agent.py +177 -0
  41. databricks_agentbricks/templates/agent-langgraph/agent/mcps.py +27 -0
  42. databricks_agentbricks/templates/agent-langgraph/agent/tools/__init__.py +24 -0
  43. databricks_agentbricks/templates/agent-langgraph/agent/tools/sample_tool.py +15 -0
  44. databricks_agentbricks/templates/agent-langgraph/agent/tools/send_message.py +17 -0
  45. databricks_agentbricks/templates/agent-langgraph/app.yaml +15 -0
  46. databricks_agentbricks/templates/agent-langgraph/pyproject.toml +42 -0
  47. databricks_agentbricks/templates/agent-langgraph/runtime/__init__.py +0 -0
  48. databricks_agentbricks/templates/agent-langgraph/runtime/adapter.py +144 -0
  49. databricks_agentbricks/templates/agent-langgraph/runtime/main.py +25 -0
  50. databricks_agentbricks/templates/agent-langgraph/tests/__init__.py +0 -0
  51. databricks_agentbricks/templates/agent-langgraph/tests/test_agent.py +211 -0
  52. databricks_agentbricks/templates/agent-langgraph/tests/test_runtime.py +19 -0
  53. databricks_agentbricks/templates/agent-langgraph/tests/test_workspace.py +28 -0
  54. databricks_agentbricks/templates/agent-openai/.env.example +37 -0
  55. databricks_agentbricks/templates/agent-openai/.gitignore +199 -0
  56. databricks_agentbricks/templates/agent-openai/AGENTKIT_CONTRACT.md +107 -0
  57. databricks_agentbricks/templates/agent-openai/AGENTS.md +76 -0
  58. databricks_agentbricks/templates/agent-openai/CLAUDE.md +1 -0
  59. databricks_agentbricks/templates/agent-openai/README.md +143 -0
  60. databricks_agentbricks/templates/agent-openai/agent/__init__.py +0 -0
  61. databricks_agentbricks/templates/agent-openai/agent/agent.py +205 -0
  62. databricks_agentbricks/templates/agent-openai/agent/mcps.py +28 -0
  63. databricks_agentbricks/templates/agent-openai/agent/tools/__init__.py +24 -0
  64. databricks_agentbricks/templates/agent-openai/agent/tools/sample_tool.py +15 -0
  65. databricks_agentbricks/templates/agent-openai/agent/tools/send_message.py +18 -0
  66. databricks_agentbricks/templates/agent-openai/app.yaml +15 -0
  67. databricks_agentbricks/templates/agent-openai/pyproject.toml +42 -0
  68. databricks_agentbricks/templates/agent-openai/runtime/__init__.py +0 -0
  69. databricks_agentbricks/templates/agent-openai/runtime/adapter.py +188 -0
  70. databricks_agentbricks/templates/agent-openai/runtime/main.py +25 -0
  71. databricks_agentbricks/templates/agent-openai/tests/__init__.py +0 -0
  72. databricks_agentbricks/templates/agent-openai/tests/test_agent.py +326 -0
  73. databricks_agentbricks/templates/agent-openai/tests/test_runtime.py +19 -0
  74. databricks_agentbricks/templates/agent-openai/tests/test_workspace.py +28 -0
  75. databricks_agentbricks/templates/custom-agent-langgraph/.env.example +1 -0
  76. databricks_agentbricks/templates/custom-agent-langgraph/.gitignore +11 -0
  77. databricks_agentbricks/templates/custom-agent-langgraph/README.md +22 -0
  78. databricks_agentbricks/templates/custom-agent-langgraph/agent/__init__.py +0 -0
  79. databricks_agentbricks/templates/custom-agent-langgraph/agent/agent.py +27 -0
  80. databricks_agentbricks/templates/custom-agent-langgraph/app.yaml +1 -0
  81. databricks_agentbricks/templates/custom-agent-langgraph/pyproject.toml +38 -0
  82. databricks_agentbricks/templates/custom-agent-langgraph/runtime/__init__.py +0 -0
  83. databricks_agentbricks/templates/custom-agent-langgraph/runtime/main.py +31 -0
  84. databricks_agentbricks/templates/custom-agent-langgraph/tests/test_runtime.py +29 -0
  85. databricks_agentbricks/templates/custom-agent-openai/.env.example +1 -0
  86. databricks_agentbricks/templates/custom-agent-openai/.gitignore +11 -0
  87. databricks_agentbricks/templates/custom-agent-openai/README.md +22 -0
  88. databricks_agentbricks/templates/custom-agent-openai/agent/__init__.py +0 -0
  89. databricks_agentbricks/templates/custom-agent-openai/agent/agent.py +29 -0
  90. databricks_agentbricks/templates/custom-agent-openai/app.yaml +1 -0
  91. databricks_agentbricks/templates/custom-agent-openai/pyproject.toml +38 -0
  92. databricks_agentbricks/templates/custom-agent-openai/runtime/__init__.py +0 -0
  93. databricks_agentbricks/templates/custom-agent-openai/runtime/main.py +31 -0
  94. databricks_agentbricks/templates/custom-agent-openai/tests/test_runtime.py +29 -0
  95. databricks_agentbricks/templates/sandbox_mcp.py +39 -0
  96. databricks_agentbricks/templates/sandbox_mcp_langgraph.py +39 -0
  97. databricks_agentbricks/templates/ui/agent-langgraph/CHAT_APP.md +49 -0
  98. databricks_agentbricks/templates/ui/agent-langgraph/runtime/main.py +26 -0
  99. databricks_agentbricks/templates/ui/agent-langgraph/runtime/ui.py +528 -0
  100. databricks_agentbricks/templates/ui/agent-langgraph/tests/test_demo_ui.py +542 -0
  101. databricks_agentbricks/templates/ui/agent-langgraph/ui/agentbricks-mark.png +0 -0
  102. databricks_agentbricks/templates/ui/agent-langgraph/ui/app.js +1464 -0
  103. databricks_agentbricks/templates/ui/agent-langgraph/ui/index.html +281 -0
  104. databricks_agentbricks/templates/ui/agent-langgraph/ui/styles.css +1913 -0
  105. databricks_agentbricks/templates/ui/agent-openai/CHAT_APP.md +54 -0
  106. databricks_agentbricks/templates/ui/agent-openai/runtime/main.py +26 -0
  107. databricks_agentbricks/templates/ui/agent-openai/runtime/ui.py +521 -0
  108. databricks_agentbricks/templates/ui/agent-openai/tests/test_demo_ui.py +512 -0
  109. databricks_agentbricks/templates/ui/agent-openai/ui/agentbricks-mark.png +0 -0
  110. databricks_agentbricks/templates/ui/agent-openai/ui/app.js +1428 -0
  111. databricks_agentbricks/templates/ui/agent-openai/ui/index.html +281 -0
  112. databricks_agentbricks/templates/ui/agent-openai/ui/styles.css +1913 -0
  113. databricks_agentbricks/theme.py +72 -0
  114. databricks_agentbricks-0.2.0.dist-info/METADATA +897 -0
  115. databricks_agentbricks-0.2.0.dist-info/RECORD +159 -0
  116. databricks_agentbricks-0.2.0.dist-info/WHEEL +4 -0
  117. databricks_agentbricks-0.2.0.dist-info/entry_points.txt +2 -0
  118. databricks_agentbricks-0.2.0.dist-info/licenses/NOTICE +39 -0
  119. databricks_agentkit/__init__.py +67 -0
  120. databricks_agentkit/_api_client.py +673 -0
  121. databricks_agentkit/_pagination.py +25 -0
  122. databricks_agentkit/client.py +31 -0
  123. databricks_agentkit/langgraph/__init__.py +98 -0
  124. databricks_agentkit/langgraph/genie.py +50 -0
  125. databricks_agentkit/langgraph/mcp.py +216 -0
  126. databricks_agentkit/langgraph/memory.py +67 -0
  127. databricks_agentkit/langgraph/session_store.py +485 -0
  128. databricks_agentkit/memory_store.py +371 -0
  129. databricks_agentkit/models.py +330 -0
  130. databricks_agentkit/openai/__init__.py +93 -0
  131. databricks_agentkit/openai/genie.py +50 -0
  132. databricks_agentkit/openai/mcp.py +189 -0
  133. databricks_agentkit/openai/memory.py +67 -0
  134. databricks_agentkit/openai/sessions.py +157 -0
  135. databricks_agentkit/py.typed +0 -0
  136. databricks_agentkit/runtime/ARCHITECTURE.md +64 -0
  137. databricks_agentkit/runtime/README.md +184 -0
  138. databricks_agentkit/runtime/__init__.py +69 -0
  139. databricks_agentkit/runtime/app.py +316 -0
  140. databricks_agentkit/runtime/auth.py +149 -0
  141. databricks_agentkit/runtime/durability/__init__.py +1 -0
  142. databricks_agentkit/runtime/durability/execution.py +133 -0
  143. databricks_agentkit/runtime/durability/heartbeat.py +64 -0
  144. databricks_agentkit/runtime/durability/lakebase_runtime_store.py +648 -0
  145. databricks_agentkit/runtime/durability/recovery.py +70 -0
  146. databricks_agentkit/runtime/durability/store.py +33 -0
  147. databricks_agentkit/runtime/execution.py +160 -0
  148. databricks_agentkit/runtime/genie.py +224 -0
  149. databricks_agentkit/runtime/mcp_auth.py +114 -0
  150. databricks_agentkit/runtime/model_services.py +90 -0
  151. databricks_agentkit/runtime/runtime.py +273 -0
  152. databricks_agentkit/runtime/session_store_client.py +120 -0
  153. databricks_agentkit/runtime/store.py +287 -0
  154. databricks_agentkit/runtime/tool_manifest.py +223 -0
  155. databricks_agentkit/runtime/tracing.py +71 -0
  156. databricks_agentkit/runtime/types.py +118 -0
  157. databricks_agentkit/runtime/workspace.py +27 -0
  158. databricks_agentkit/session_store.py +434 -0
  159. 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}"