solace-agent-mesh 1.3.3__py3-none-any.whl → 1.4.1__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.

Potentially problematic release.


This version of solace-agent-mesh might be problematic. Click here for more details.

Files changed (89) hide show
  1. solace_agent_mesh/agent/adk/setup.py +183 -8
  2. solace_agent_mesh/agent/sac/app.py +337 -622
  3. solace_agent_mesh/agent/sac/component.py +47 -1
  4. solace_agent_mesh/agent/tools/dynamic_tool.py +36 -5
  5. solace_agent_mesh/agent/tools/tool_config_types.py +58 -0
  6. solace_agent_mesh/assets/docs/404.html +3 -3
  7. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.508ae8db.js +1 -0
  8. solace_agent_mesh/assets/docs/assets/js/9a09e75d.92de8cf5.js +1 -0
  9. solace_agent_mesh/assets/docs/assets/js/ae4415af.16cc58d3.js +1 -0
  10. solace_agent_mesh/assets/docs/assets/js/{main.e82b32e6.js → main.9bc1a102.js} +2 -2
  11. solace_agent_mesh/assets/docs/assets/js/{runtime~main.aad1f874.js → runtime~main.f2b4ea70.js} +1 -1
  12. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +3 -3
  13. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +3 -3
  14. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
  15. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +3 -3
  16. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +3 -3
  17. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  18. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +20 -5
  19. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
  20. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  21. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +3 -3
  22. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +3 -3
  23. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  24. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  25. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +3 -3
  26. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
  27. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +3 -3
  28. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
  29. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +3 -3
  30. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +3 -3
  31. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +3 -3
  32. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +3 -3
  33. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +3 -3
  34. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +3 -3
  35. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +3 -3
  36. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +3 -3
  37. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +3 -3
  38. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +3 -3
  39. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
  40. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +3 -3
  41. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +3 -3
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  43. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +3 -3
  44. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -5
  45. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +3 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +68 -3
  47. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +3 -3
  50. solace_agent_mesh/assets/docs/lunr-index-1758036158289.json +1 -0
  51. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  52. solace_agent_mesh/assets/docs/search-doc-1758036158289.json +1 -0
  53. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  54. solace_agent_mesh/cli/__init__.py +1 -1
  55. solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +2 -0
  56. solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +10 -245
  57. solace_agent_mesh/cli/commands/plugin_cmd/install_cmd.py +283 -0
  58. solace_agent_mesh/cli/commands/run_cmd.py +4 -7
  59. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-CAX9u8a7.js → authCallback-j1LW-wlq.js} +1 -1
  60. solace_agent_mesh/client/webui/frontend/static/assets/{client-DXU9SPI5.js → client-B9p_nFNA.js} +1 -1
  61. solace_agent_mesh/client/webui/frontend/static/assets/main-B6BpuH9K.js +339 -0
  62. solace_agent_mesh/client/webui/frontend/static/assets/main-B9s_V9tJ.css +1 -0
  63. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-B0BEKoAR.js → vendor-CS5YMf8a.js} +74 -69
  64. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  65. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  66. solace_agent_mesh/common/services/identity_service.py +2 -1
  67. solace_agent_mesh/common/services/providers/local_file_identity_service.py +1 -1
  68. solace_agent_mesh/common/utils/pydantic_utils.py +60 -0
  69. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +6 -4
  70. solace_agent_mesh/gateway/base/app.py +69 -120
  71. solace_agent_mesh/gateway/http_sse/app.py +99 -150
  72. solace_agent_mesh/gateway/http_sse/component.py +57 -30
  73. solace_agent_mesh/gateway/http_sse/main.py +337 -375
  74. solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +87 -0
  75. solace_agent_mesh/gateway/http_sse/sse_manager.py +44 -23
  76. solace_agent_mesh/templates/webui.yaml +1 -1
  77. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.1.dist-info}/METADATA +7 -1
  78. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.1.dist-info}/RECORD +82 -78
  79. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.3f34bf76.js +0 -1
  80. solace_agent_mesh/assets/docs/assets/js/9a09e75d.5a319fd4.js +0 -1
  81. solace_agent_mesh/assets/docs/assets/js/ae4415af.24cdc514.js +0 -1
  82. solace_agent_mesh/assets/docs/lunr-index-1757873594308.json +0 -1
  83. solace_agent_mesh/assets/docs/search-doc-1757873594308.json +0 -1
  84. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +0 -1
  85. solace_agent_mesh/client/webui/frontend/static/assets/main-DjoMeldu.js +0 -339
  86. /solace_agent_mesh/assets/docs/assets/js/{main.e82b32e6.js.LICENSE.txt → main.9bc1a102.js.LICENSE.txt} +0 -0
  87. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.1.dist-info}/WHEEL +0 -0
  88. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.1.dist-info}/entry_points.txt +0 -0
  89. {solace_agent_mesh-1.3.3.dist-info → solace_agent_mesh-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
1
- __version__ = "1.3.3"
1
+ __version__ = "1.4.1"
@@ -1,6 +1,7 @@
1
1
  import click
2
2
 
3
3
  from .create_cmd import create_plugin_cmd
4
+ from .install_cmd import install_plugin_cmd
4
5
  from .add_cmd import add_plugin_component_cmd
5
6
  from .build_cmd import build_plugin_cmd
6
7
  from .catalog_cmd import catalog
@@ -13,6 +14,7 @@ def plugin():
13
14
 
14
15
 
15
16
  plugin.add_command(create_plugin_cmd, name="create")
17
+ plugin.add_command(install_plugin_cmd, name="install")
16
18
  plugin.add_command(add_plugin_component_cmd, name="add")
17
19
  plugin.add_command(build_plugin_cmd, name="build")
18
20
  plugin.add_command(catalog, name="catalog")
@@ -1,55 +1,15 @@
1
1
  import click
2
2
  import pathlib
3
- import subprocess
4
- import tempfile
5
- import shutil
6
- import os
7
- import re
8
3
  import toml
9
4
 
10
- from cli.utils import get_formatted_names, get_module_path, error_exit
11
- from .official_registry import get_official_plugin_url
5
+ from cli.utils import get_formatted_names, error_exit
6
+ from .install_cmd import install_plugin
12
7
 
13
8
 
14
9
  def ensure_directory_exists(path: pathlib.Path):
15
10
  """Creates a directory if it doesn't exist."""
16
11
  path.mkdir(parents=True, exist_ok=True)
17
12
 
18
-
19
- def _check_command_exists(command: str) -> bool:
20
- """Checks if a command exists on the system."""
21
- return shutil.which(command) is not None
22
-
23
-
24
- def _get_plugin_name_from_source_pyproject(source_path: pathlib.Path) -> str | None:
25
- """Reads pyproject.toml from source_path and returns the project name."""
26
- pyproject_path = source_path / "pyproject.toml"
27
- if not pyproject_path.is_file():
28
- click.echo(
29
- click.style(
30
- f"Warning: pyproject.toml not found at {pyproject_path}. Cannot determine module name automatically.",
31
- fg="yellow",
32
- )
33
- )
34
- return None
35
- try:
36
- with open(pyproject_path, "r", encoding="utf-8") as f:
37
- data = toml.load(f)
38
- project_name = data.get("project", {}).get("name")
39
- if project_name:
40
- return project_name.strip().replace("-", "_") # Normalize to snake_case
41
- click.echo(
42
- click.style(
43
- f"Warning: Could not find 'project.name' in {pyproject_path}.",
44
- fg="yellow",
45
- )
46
- )
47
- return None
48
- except Exception as e:
49
- click.echo(click.style(f"Error parsing {pyproject_path}: {e}", fg="red"))
50
- return None
51
-
52
-
53
13
  def _get_plugin_type_from_pyproject(source_path: pathlib.Path) -> str | None:
54
14
  """Reads pyproject.toml from source_path and returns the plugin type."""
55
15
  pyproject_path = source_path / "pyproject.toml"
@@ -82,37 +42,6 @@ def _get_plugin_type_from_pyproject(source_path: pathlib.Path) -> str | None:
82
42
  return None
83
43
 
84
44
 
85
- def _run_install(
86
- install_command, install_target: str | pathlib.Path, operation_desc: str
87
- ) -> str | None:
88
- """Runs install for the given target."""
89
- click.echo(
90
- f"Attempting to install plugin using {install_command} from {operation_desc}..."
91
- )
92
- try:
93
- process = subprocess.run(
94
- install_command.format(package=str(install_target)).split(),
95
- capture_output=True,
96
- text=True,
97
- check=False,
98
- )
99
- if process.returncode == 0:
100
- click.echo(
101
- click.style(
102
- f"Plugin successfully installed via from {operation_desc}.",
103
- fg="green",
104
- )
105
- )
106
- if process.stdout:
107
- click.echo(f"install output:\n{process.stdout}")
108
- return None
109
- else:
110
- return f"Error: 'install {install_target}' failed.\nstdout:\n{process.stdout}\nstderr:\n{process.stderr}"
111
- except FileNotFoundError:
112
- return "Error: 'python' or command not found. Ensure Python and command are installed and in your PATH."
113
- except Exception as e:
114
- return f"An unexpected error occurred during install: {e}"
115
-
116
45
 
117
46
  @click.command("add")
118
47
  @click.argument("component_name")
@@ -130,189 +59,25 @@ def _run_install(
130
59
  def add_plugin_component_cmd(
131
60
  component_name: str, plugin_source: str, installer_command: str | None = None
132
61
  ):
133
- """Creates a new component instance from a specified plugin source."""
62
+ """Installs the plugin and creates a new component instance from a specified plugin source."""
134
63
 
135
64
  click.echo(
136
65
  f"Attempting to add component '{component_name}' using plugin source '{plugin_source}'..."
137
66
  )
138
- if not installer_command:
139
- installer_command = os.environ.get(
140
- "SAM_PLUGIN_INSTALL_COMMAND", "pip3 install {package}"
141
- )
142
- try:
143
- installer_command.format(package="dummy") # Test if the command is valid
144
- except (KeyError, ValueError):
145
- return error_exit(
146
- "Error: The installer command must contain a placeholder '{package}' to be replaced with the actual package name."
147
- )
148
-
149
- official_plugin_url = get_official_plugin_url(plugin_source)
150
- if official_plugin_url:
151
- click.echo(f"Found official plugin '{plugin_source}' at: {official_plugin_url}")
152
- plugin_source = official_plugin_url
153
-
154
- install_type = None # "module", "local", "git"
155
- module_name_to_load = None
156
- install_target = None
157
- source_path_for_name_extraction = None
158
-
159
- if plugin_source.startswith(("http://", "https://")) and plugin_source.endswith(
160
- ".git"
161
- ):
162
- install_type = "repository"
163
- install_target = plugin_source
164
- elif plugin_source.startswith(("git+")):
165
- install_type = "git"
166
- install_target = plugin_source
167
- elif os.path.exists(plugin_source):
168
- local_path = pathlib.Path(plugin_source).resolve()
169
- if local_path.is_dir():
170
- install_type = "local"
171
- install_target = str(local_path)
172
- source_path_for_name_extraction = local_path
173
- elif local_path.is_file() and local_path.suffix in [".whl", ".tar.gz"]:
174
- install_type = "wheel"
175
- install_target = str(local_path)
176
- else:
177
- return error_exit(
178
- f"Error: Local path '{plugin_source}' exists but is not a directory or wheel."
179
- )
180
- elif not re.search(r"[/\\]", plugin_source):
181
- install_type = "module"
182
- module_name_to_load = plugin_source.strip().replace("-", "_")
183
- else:
184
- return error_exit(
185
- f"Error: Invalid plugin source '{plugin_source}'. Not a recognized module name, local path, or Git URL."
186
- )
187
67
 
188
- if install_type in ["local", "git", "repository", "wheel"]:
189
- if install_type == "repository":
190
- if not _check_command_exists("git"):
191
- return error_exit(
192
- "Error: 'git' command not found. Please install Git or install the plugin manually."
193
- )
194
-
195
- with tempfile.TemporaryDirectory() as temp_dir:
196
- temp_dir_path = pathlib.Path(temp_dir)
197
- cloned_repo_path = temp_dir_path / "plugin_repo"
198
- click.echo(
199
- f"Cloning Git repository '{plugin_source}' to temporary directory {cloned_repo_path}..."
200
- )
201
- try:
202
- subprocess.run(
203
- ["git", "clone", plugin_source, str(cloned_repo_path)],
204
- capture_output=True,
205
- text=True,
206
- check=True,
207
- )
208
- source_path_for_name_extraction = cloned_repo_path
209
- except subprocess.CalledProcessError as e:
210
- return error_exit(f"Error cloning Git repository: {e.stderr}")
211
- except FileNotFoundError:
212
- return error_exit("Error: 'git' command not found during clone.")
213
-
214
- module_name_from_pyproject = _get_plugin_name_from_source_pyproject(
215
- source_path_for_name_extraction
216
- )
217
- if not module_name_from_pyproject:
218
- return error_exit(
219
- "Could not determine module name from pyproject.toml in the Git repo. Aborting."
220
- )
221
-
222
- err = _run_install(
223
- installer_command, install_target, f"Git URL ({plugin_source})"
224
- )
225
- if err:
226
- return error_exit(err)
227
- module_name_to_load = module_name_from_pyproject
228
-
229
- elif install_type == "git":
230
- module_name_from_url = (
231
- plugin_source.split("#")[0]
232
- .split("?")[0]
233
- .split("/")[-1]
234
- .replace(".git", "")
235
- .replace("-", "_")
236
- )
237
- if "#subdirectory=" in plugin_source:
238
- module_name_from_url = (
239
- plugin_source.split("#subdirectory=")[-1]
240
- .split("?")[0]
241
- .replace(".git", "")
242
- .replace("-", "_")
243
- )
244
-
245
- if not module_name_from_url:
246
- return error_exit(
247
- f"Could not determine module name from the Git URL {plugin_source}. Aborting."
248
- )
249
-
250
- err = _run_install(
251
- installer_command, install_target, f"Git URL ({plugin_source})"
252
- )
253
- if err:
254
- return error_exit(err)
255
- module_name_to_load = module_name_from_url
256
-
257
- elif install_type == "local":
258
- module_name_from_pyproject = _get_plugin_name_from_source_pyproject(
259
- source_path_for_name_extraction
260
- )
261
- if not module_name_from_pyproject:
262
- return error_exit(
263
- f"Could not determine module name from pyproject.toml at {source_path_for_name_extraction}. Aborting."
264
- )
265
-
266
- err = _run_install(
267
- installer_command, install_target, f"local path ({install_target})"
268
- )
269
- if err:
270
- return error_exit(err)
271
- module_name_to_load = module_name_from_pyproject
272
-
273
- elif install_type == "wheel":
274
- module_name_from_wheel = (
275
- pathlib.Path(install_target).stem.split("-")[0]
276
- )
277
- if not module_name_from_wheel:
278
- return error_exit(
279
- f"Could not determine module name from the wheel file {install_target}. Aborting."
280
- )
281
-
282
- err = _run_install(
283
- installer_command, install_target, f"wheel file ({install_target})"
284
- )
285
- if err:
286
- return error_exit(err)
287
- module_name_to_load = module_name_from_wheel
288
-
289
- if not module_name_to_load:
290
- return error_exit("Error: Could not determine the plugin module name to load.")
291
-
292
- click.echo(f"Proceeding to load plugin module '{module_name_to_load}'...")
293
- try:
294
- plugin_root_path = pathlib.Path(get_module_path(module_name_to_load))
295
- except ImportError:
296
- return error_exit(
297
- f"Error: Plugin module '{module_name_to_load}' not found after potential installation. Please check installation logs or install manually."
298
- )
299
-
300
- if not plugin_root_path or not plugin_root_path.exists():
301
- return error_exit(
302
- f"Error: Could not determine a valid root path for plugin module '{module_name_to_load}'. Path: {plugin_root_path}"
303
- )
68
+ module_name, plugin_path = install_plugin(plugin_source, installer_command)
304
69
 
305
- plugin_config_path = plugin_root_path / "config.yaml"
306
- plugin_pyproject_path = plugin_root_path / "pyproject.toml"
70
+ plugin_config_path = plugin_path / "config.yaml"
71
+ plugin_pyproject_path = plugin_path / "pyproject.toml"
307
72
 
308
73
  if not plugin_pyproject_path.is_file():
309
74
  return error_exit(
310
- f"Error: pyproject.toml not found in plugin '{module_name_to_load}' at expected path {plugin_pyproject_path}"
75
+ f"Error: pyproject.toml not found in plugin '{module_name}' at expected path {plugin_pyproject_path}"
311
76
  )
312
77
 
313
78
  if not plugin_config_path.is_file():
314
79
  return error_exit(
315
- f"Error: config.yaml not found in plugin '{module_name_to_load}' at expected path {plugin_config_path}"
80
+ f"Error: config.yaml not found in plugin '{module_name}' at expected path {plugin_config_path}"
316
81
  )
317
82
 
318
83
  try:
@@ -341,7 +106,7 @@ def add_plugin_component_cmd(
341
106
  for placeholder, value in component_replacements.items():
342
107
  processed_config_content = processed_config_content.replace(placeholder, value)
343
108
 
344
- plugin_type = _get_plugin_type_from_pyproject(plugin_root_path)
109
+ plugin_type = _get_plugin_type_from_pyproject(plugin_path)
345
110
  if plugin_type == "agent":
346
111
  target_dir = pathlib.Path("configs/agents")
347
112
  elif plugin_type == "gateway":
@@ -362,7 +127,7 @@ def add_plugin_component_cmd(
362
127
  click.echo(f" Created component configuration: {target_path}")
363
128
  click.echo(
364
129
  click.style(
365
- f"Component '{component_name}' created successfully from plugin '{module_name_to_load}'.",
130
+ f"Component '{component_name}' created successfully from plugin '{module_name}'.",
366
131
  fg="green",
367
132
  )
368
133
  )
@@ -0,0 +1,283 @@
1
+ import tempfile
2
+ import os
3
+ import re
4
+ import subprocess
5
+ import pathlib
6
+ import click
7
+ import shutil
8
+ import toml
9
+
10
+ from cli.utils import get_module_path, error_exit
11
+ from .official_registry import get_official_plugin_url
12
+
13
+
14
+ def _check_command_exists(command: str) -> bool:
15
+ """Checks if a command exists on the system."""
16
+ return shutil.which(command) is not None
17
+
18
+ def _get_plugin_name_from_source_pyproject(source_path: pathlib.Path) -> str | None:
19
+ """Reads pyproject.toml from source_path and returns the project name."""
20
+ pyproject_path = source_path / "pyproject.toml"
21
+ if not pyproject_path.is_file():
22
+ click.echo(
23
+ click.style(
24
+ f"Warning: pyproject.toml not found at {pyproject_path}. Cannot determine module name automatically.",
25
+ fg="yellow",
26
+ )
27
+ )
28
+ return None
29
+ try:
30
+ with open(pyproject_path, "r", encoding="utf-8") as f:
31
+ data = toml.load(f)
32
+ project_name = data.get("project", {}).get("name")
33
+ if project_name:
34
+ return project_name.strip().replace("-", "_") # Normalize to snake_case
35
+ click.echo(
36
+ click.style(
37
+ f"Warning: Could not find 'project.name' in {pyproject_path}.",
38
+ fg="yellow",
39
+ )
40
+ )
41
+ return None
42
+ except Exception as e:
43
+ click.echo(click.style(f"Error parsing {pyproject_path}: {e}", fg="red"))
44
+ return None
45
+
46
+ def _run_install(
47
+ install_command, install_target: str | pathlib.Path, operation_desc: str
48
+ ) -> str | None:
49
+ """Runs install for the given target."""
50
+ click.echo(
51
+ f"Attempting to install plugin using {install_command} from {operation_desc}..."
52
+ )
53
+ try:
54
+ process = subprocess.run(
55
+ install_command.format(package=str(install_target)).split(),
56
+ capture_output=True,
57
+ text=True,
58
+ check=False,
59
+ )
60
+ if process.returncode == 0:
61
+ click.echo(
62
+ click.style(
63
+ f"Plugin successfully installed via from {operation_desc}.",
64
+ fg="green",
65
+ )
66
+ )
67
+ if process.stdout:
68
+ click.echo(f"install output:\n{process.stdout}")
69
+ return None
70
+ else:
71
+ return f"Error: 'install {install_target}' failed.\nstdout:\n{process.stdout}\nstderr:\n{process.stderr}"
72
+ except FileNotFoundError:
73
+ return "Error: 'python' or command not found. Ensure Python and command are installed and in your PATH."
74
+ except Exception as e:
75
+ return f"An unexpected error occurred during install: {e}"
76
+
77
+ def install_plugin(plugin_source: str, installer_command: str | None = None) -> tuple[str | None, pathlib.Path | None]:
78
+ """Installs a plugin from the specified source.
79
+
80
+ Args:
81
+ plugin_source: Source of the plugin (module name, local path, or Git URL)
82
+ installer_command: Command to install the plugin, with '{package}' placeholder
83
+ Returns:
84
+ Tuple of (module_name, plugin_path) if successful, or (None, None) on failure
85
+ """
86
+ if not installer_command:
87
+ installer_command = os.environ.get(
88
+ "SAM_PLUGIN_INSTALL_COMMAND", "pip3 install {package}"
89
+ )
90
+ try:
91
+ installer_command.format(package="dummy") # Test if the command is valid
92
+ except (KeyError, ValueError):
93
+ return error_exit(
94
+ "Error: The installer command must contain a placeholder '{package}' to be replaced with the actual package name."
95
+ )
96
+
97
+ official_plugin_url = get_official_plugin_url(plugin_source)
98
+ if official_plugin_url:
99
+ click.echo(f"Found official plugin '{plugin_source}' at: {official_plugin_url}")
100
+ plugin_source = official_plugin_url
101
+
102
+ install_type = None # "module", "local", "git"
103
+ module_name = None
104
+ install_target = None
105
+ source_path_for_name_extraction = None
106
+
107
+ if plugin_source.startswith(("http://", "https://")) and plugin_source.endswith(
108
+ ".git"
109
+ ):
110
+ install_type = "repository"
111
+ install_target = plugin_source
112
+ elif plugin_source.startswith(("git+")):
113
+ install_type = "git"
114
+ install_target = plugin_source
115
+ elif os.path.exists(plugin_source):
116
+ local_path = pathlib.Path(plugin_source).resolve()
117
+ if local_path.is_dir():
118
+ install_type = "local"
119
+ install_target = str(local_path)
120
+ source_path_for_name_extraction = local_path
121
+ elif local_path.is_file() and local_path.suffix in [".whl", ".tar.gz"]:
122
+ install_type = "wheel"
123
+ install_target = str(local_path)
124
+ else:
125
+ return error_exit(
126
+ f"Error: Local path '{plugin_source}' exists but is not a directory or wheel."
127
+ )
128
+ elif not re.search(r"[/\\]", plugin_source):
129
+ install_type = "module"
130
+ module_name = plugin_source.strip().replace("-", "_")
131
+ else:
132
+ return error_exit(
133
+ f"Error: Invalid plugin source '{plugin_source}'. Not a recognized module name, local path, or Git URL."
134
+ )
135
+
136
+ if install_type in ["local", "git", "repository", "wheel"]:
137
+ if install_type == "repository":
138
+ if not _check_command_exists("git"):
139
+ return error_exit(
140
+ "Error: 'git' command not found. Please install Git or install the plugin manually."
141
+ )
142
+
143
+ with tempfile.TemporaryDirectory() as temp_dir:
144
+ temp_dir_path = pathlib.Path(temp_dir)
145
+ cloned_repo_path = temp_dir_path / "plugin_repo"
146
+ click.echo(
147
+ f"Cloning Git repository '{plugin_source}' to temporary directory {cloned_repo_path}..."
148
+ )
149
+ try:
150
+ subprocess.run(
151
+ ["git", "clone", plugin_source, str(cloned_repo_path)],
152
+ capture_output=True,
153
+ text=True,
154
+ check=True,
155
+ )
156
+ source_path_for_name_extraction = cloned_repo_path
157
+ except subprocess.CalledProcessError as e:
158
+ return error_exit(f"Error cloning Git repository: {e.stderr}")
159
+ except FileNotFoundError:
160
+ return error_exit("Error: 'git' command not found during clone.")
161
+
162
+ module_name_from_pyproject = _get_plugin_name_from_source_pyproject(
163
+ source_path_for_name_extraction
164
+ )
165
+ if not module_name_from_pyproject:
166
+ return error_exit(
167
+ "Could not determine module name from pyproject.toml in the Git repo. Aborting."
168
+ )
169
+
170
+ err = _run_install(
171
+ installer_command, install_target, f"Git URL ({plugin_source})"
172
+ )
173
+ if err:
174
+ return error_exit(err)
175
+ module_name = module_name_from_pyproject
176
+
177
+ elif install_type == "git":
178
+ module_name_from_url = (
179
+ plugin_source.split("#")[0]
180
+ .split("?")[0]
181
+ .split("/")[-1]
182
+ .replace(".git", "")
183
+ .replace("-", "_")
184
+ )
185
+ if "#subdirectory=" in plugin_source:
186
+ module_name_from_url = (
187
+ plugin_source.split("#subdirectory=")[-1]
188
+ .split("?")[0]
189
+ .replace(".git", "")
190
+ .replace("-", "_")
191
+ )
192
+
193
+ if not module_name_from_url:
194
+ return error_exit(
195
+ f"Could not determine module name from the Git URL {plugin_source}. Aborting."
196
+ )
197
+
198
+ err = _run_install(
199
+ installer_command, install_target, f"Git URL ({plugin_source})"
200
+ )
201
+ if err:
202
+ return error_exit(err)
203
+ module_name = module_name_from_url
204
+
205
+ elif install_type == "local":
206
+ module_name_from_pyproject = _get_plugin_name_from_source_pyproject(
207
+ source_path_for_name_extraction
208
+ )
209
+ if not module_name_from_pyproject:
210
+ return error_exit(
211
+ f"Could not determine module name from pyproject.toml at {source_path_for_name_extraction}. Aborting."
212
+ )
213
+
214
+ err = _run_install(
215
+ installer_command, install_target, f"local path ({install_target})"
216
+ )
217
+ if err:
218
+ return error_exit(err)
219
+ module_name = module_name_from_pyproject
220
+
221
+ elif install_type == "wheel":
222
+ module_name_from_wheel = (
223
+ pathlib.Path(install_target).stem.split("-")[0]
224
+ )
225
+ if not module_name_from_wheel:
226
+ return error_exit(
227
+ f"Could not determine module name from the wheel file {install_target}. Aborting."
228
+ )
229
+
230
+ err = _run_install(
231
+ installer_command, install_target, f"wheel file ({install_target})"
232
+ )
233
+ if err:
234
+ return error_exit(err)
235
+ module_name = module_name_from_wheel
236
+
237
+ if not module_name:
238
+ return error_exit("Error: Could not determine the plugin module name to load.")
239
+
240
+ click.echo(f"Proceeding to load plugin module '{module_name}'...")
241
+ try:
242
+ plugin_path = pathlib.Path(get_module_path(module_name))
243
+ except ImportError:
244
+ return error_exit(
245
+ f"Error: Plugin module '{module_name}' not found after potential installation. Please check installation logs or install manually."
246
+ )
247
+
248
+ if not plugin_path or not plugin_path.exists():
249
+ return error_exit(
250
+ f"Error: Could not determine a valid root path for plugin module '{module_name}'. Path: {plugin_path}"
251
+ )
252
+
253
+ return module_name, plugin_path
254
+
255
+
256
+ @click.command("install")
257
+ @click.argument("plugin_source")
258
+ @click.option(
259
+ "--install-command",
260
+ "installer_command",
261
+ help="Command to use to install a python package. Must follow the format 'command {package} args', by default 'pip3 install {package}'. Can also be set through the environment variable SAM_PLUGIN_INSTALL_COMMAND.",
262
+ )
263
+ def install_plugin_cmd(
264
+ plugin_source: str,
265
+ installer_command: str | None = None
266
+ ):
267
+ """
268
+ Installs a plugin from the specified source.
269
+
270
+ PLUGIN_SOURCE can be:
271
+ - A local path to a directory (e.g., '/path/to/plugin')
272
+ - A local path to a wheel file (e.g., '/path/to/plugin.whl')
273
+ - A Git URL (e.g., 'https://github.com/user/repo.git')
274
+ - The name of the plugin from https://github.com/SolaceLabs/solace-agent-mesh-core-plugins
275
+ """
276
+ module_name, plugin_path = install_plugin(plugin_source, installer_command)
277
+ if module_name and plugin_path:
278
+ click.echo(
279
+ click.style(
280
+ f"Plugin '{module_name}' installed and available at {plugin_path}.",
281
+ fg="green",
282
+ )
283
+ )
@@ -119,7 +119,7 @@ def run(files: tuple[str, ...], skip_files: tuple[str, ...], system_env: bool):
119
119
  "shared_config"
120
120
  ):
121
121
  click.echo(
122
- f" Skipping discovery: {filepath.relative_to(project_root)} (underscore prefix or shared_config)"
122
+ f" Skipping discovery: {filepath} (underscore prefix or shared_config)"
123
123
  )
124
124
  continue
125
125
  config_files_to_run.append(str(filepath.resolve()))
@@ -129,7 +129,7 @@ def run(files: tuple[str, ...], skip_files: tuple[str, ...], system_env: bool):
129
129
  "shared_config"
130
130
  ):
131
131
  click.echo(
132
- f" Skipping discovery: {filepath.relative_to(project_root)} (underscore prefix or shared_config)"
132
+ f" Skipping discovery: {filepath} (underscore prefix or shared_config)"
133
133
  )
134
134
  continue
135
135
  if str(filepath.resolve()) not in config_files_to_run:
@@ -148,7 +148,7 @@ def run(files: tuple[str, ...], skip_files: tuple[str, ...], system_env: bool):
148
148
  "shared_config"
149
149
  ):
150
150
  click.echo(
151
- f" Skipping discovery: {filepath.relative_to(path)} (underscore prefix or shared_config)"
151
+ f" Skipping discovery: {filepath} (underscore prefix or shared_config)"
152
152
  )
153
153
  continue
154
154
  processed_files.add(str(filepath.resolve()))
@@ -168,7 +168,7 @@ def run(files: tuple[str, ...], skip_files: tuple[str, ...], system_env: bool):
168
168
  for cf in config_files_to_run:
169
169
  if os.path.basename(cf) in skipped_basenames:
170
170
  click.echo(
171
- f" Skipping execution: {Path(cf).relative_to(project_root)} (due to --skip)"
171
+ f" Skipping execution: {cf} (due to --skip)"
172
172
  )
173
173
  continue
174
174
  final_list.append(cf)
@@ -184,9 +184,6 @@ def run(files: tuple[str, ...], skip_files: tuple[str, ...], system_env: bool):
184
184
 
185
185
  click.echo(click.style("Final list of configuration files to run:", bold=True))
186
186
  for cf_path_str in config_files_to_run:
187
- try:
188
- click.echo(f" - {Path(cf_path_str).relative_to(project_root)}")
189
- except ValueError:
190
187
  click.echo(f" - {cf_path_str}")
191
188
 
192
189
  return_code = _execute_with_solace_ai_connector(config_files_to_run)
@@ -1 +1 @@
1
- import{c as n}from"./client-DXU9SPI5.js";import{r as a,j as s}from"./vendor-B0BEKoAR.js";function c(){return a.useEffect(()=>{const r=window.location.hash.substring(1),e=new URLSearchParams(r),o=e.get("access_token"),t=e.get("refresh_token");o?(localStorage.setItem("access_token",o),t&&localStorage.setItem("refresh_token",t),window.location.href="/"):console.error("AuthCallback: No access token found in URL hash.")},[]),s.jsx("div",{children:"Loading..."})}n.createRoot(document.getElementById("root")).render(s.jsx(c,{}));
1
+ import{c as n}from"./client-B9p_nFNA.js";import{r as a,j as s}from"./vendor-CS5YMf8a.js";function c(){return a.useEffect(()=>{const r=window.location.hash.substring(1),e=new URLSearchParams(r),o=e.get("access_token"),t=e.get("refresh_token");o?(localStorage.setItem("access_token",o),t&&localStorage.setItem("refresh_token",t),window.location.href="/"):console.error("AuthCallback: No access token found in URL hash.")},[]),s.jsx("div",{children:"Loading..."})}n.createRoot(document.getElementById("root")).render(s.jsx(c,{}));
@@ -1,4 +1,4 @@
1
- import{al as dd,am as sd}from"./vendor-B0BEKoAR.js";(function(){const Xl=document.createElement("link").relList;if(Xl&&Xl.supports&&Xl.supports("modulepreload"))return;for(const w of document.querySelectorAll('link[rel="modulepreload"]'))b(w);new MutationObserver(w=>{for(const tl of w)if(tl.type==="childList")for(const cu of tl.addedNodes)cu.tagName==="LINK"&&cu.rel==="modulepreload"&&b(cu)}).observe(document,{childList:!0,subtree:!0});function zl(w){const tl={};return w.integrity&&(tl.integrity=w.integrity),w.referrerPolicy&&(tl.referrerPolicy=w.referrerPolicy),w.crossOrigin==="use-credentials"?tl.credentials="include":w.crossOrigin==="anonymous"?tl.credentials="omit":tl.credentials="same-origin",tl}function b(w){if(w.ep)return;w.ep=!0;const tl=zl(w);fetch(w.href,tl)}})();var Jc={exports:{}},ne={},rc={exports:{}},wc={};/**
1
+ import{am as dd,an as sd}from"./vendor-CS5YMf8a.js";(function(){const Xl=document.createElement("link").relList;if(Xl&&Xl.supports&&Xl.supports("modulepreload"))return;for(const w of document.querySelectorAll('link[rel="modulepreload"]'))b(w);new MutationObserver(w=>{for(const tl of w)if(tl.type==="childList")for(const cu of tl.addedNodes)cu.tagName==="LINK"&&cu.rel==="modulepreload"&&b(cu)}).observe(document,{childList:!0,subtree:!0});function zl(w){const tl={};return w.integrity&&(tl.integrity=w.integrity),w.referrerPolicy&&(tl.referrerPolicy=w.referrerPolicy),w.crossOrigin==="use-credentials"?tl.credentials="include":w.crossOrigin==="anonymous"?tl.credentials="omit":tl.credentials="same-origin",tl}function b(w){if(w.ep)return;w.ep=!0;const tl=zl(w);fetch(w.href,tl)}})();var Jc={exports:{}},ne={},rc={exports:{}},wc={};/**
2
2
  * @license React
3
3
  * scheduler.production.js
4
4
  *