solace-agent-mesh 1.1.0__py3-none-any.whl → 1.3.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.

Potentially problematic release.


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

Files changed (139) hide show
  1. solace_agent_mesh/agent/adk/runner.py +18 -12
  2. solace_agent_mesh/agent/adk/services.py +3 -3
  3. solace_agent_mesh/agent/protocol/event_handlers.py +27 -21
  4. solace_agent_mesh/agent/sac/app.py +1 -1
  5. solace_agent_mesh/agent/sac/component.py +0 -1
  6. solace_agent_mesh/assets/docs/404.html +2 -2
  7. solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js → main.08d30374.js} +2 -2
  8. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +2 -2
  9. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +2 -2
  10. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +2 -2
  11. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +2 -2
  12. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +2 -2
  13. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +2 -2
  14. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +2 -2
  15. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +2 -2
  16. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +2 -2
  17. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +2 -2
  18. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +2 -2
  19. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +2 -2
  20. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +2 -2
  21. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +2 -2
  22. solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +2 -2
  23. solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +2 -2
  24. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +2 -2
  25. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +2 -2
  26. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +2 -2
  27. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +2 -2
  28. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +2 -2
  29. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +2 -2
  30. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +2 -2
  31. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +2 -2
  32. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +2 -2
  33. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +2 -2
  34. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +2 -2
  35. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +2 -2
  36. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +2 -2
  37. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +2 -2
  38. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +2 -2
  39. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +2 -2
  40. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +2 -2
  41. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +2 -2
  42. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +2 -2
  43. solace_agent_mesh/assets/docs/lunr-index-1757433031159.json +1 -0
  44. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  45. solace_agent_mesh/assets/docs/search-doc-1757433031159.json +1 -0
  46. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  47. solace_agent_mesh/cli/__init__.py +1 -1
  48. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +125 -48
  49. solace_agent_mesh/cli/commands/eval_cmd.py +14 -0
  50. solace_agent_mesh/cli/commands/init_cmd/__init__.py +53 -31
  51. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  52. solace_agent_mesh/cli/commands/init_cmd/env_step.py +19 -8
  53. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +80 -25
  54. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +32 -10
  55. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +74 -15
  56. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +0 -2
  57. solace_agent_mesh/cli/commands/run_cmd.py +5 -3
  58. solace_agent_mesh/cli/utils.py +68 -12
  59. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-vY5eu2lI.js +1 -0
  60. solace_agent_mesh/client/webui/frontend/static/assets/client-BeBkzgWW.js +25 -0
  61. solace_agent_mesh/client/webui/frontend/static/assets/main-Bjys1KQs.js +339 -0
  62. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +1 -0
  63. solace_agent_mesh/client/webui/frontend/static/assets/vendor-CE0AeXyK.js +395 -0
  64. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -2
  65. solace_agent_mesh/client/webui/frontend/static/index.html +4 -3
  66. solace_agent_mesh/common/utils/embeds/resolver.py +1 -0
  67. solace_agent_mesh/config_portal/backend/common.py +2 -2
  68. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-bFMKlzKf.js +98 -0
  69. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d845808d.js → manifest-89db7c30.js} +1 -1
  70. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  71. solace_agent_mesh/evaluation/message_organizer.py +35 -56
  72. solace_agent_mesh/evaluation/run.py +26 -5
  73. solace_agent_mesh/evaluation/subscriber.py +35 -10
  74. solace_agent_mesh/evaluation/summary_builder.py +27 -34
  75. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +676 -0
  76. solace_agent_mesh/gateway/http_sse/alembic/env.py +85 -0
  77. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  78. solace_agent_mesh/gateway/http_sse/alembic/versions/b1c2d3e4f5g6_add_database_indexes.py +83 -0
  79. solace_agent_mesh/gateway/http_sse/alembic/versions/d5b3f8f2e9a0_create_initial_database.py +58 -0
  80. solace_agent_mesh/gateway/http_sse/alembic.ini +147 -0
  81. solace_agent_mesh/gateway/http_sse/api/__init__.py +11 -0
  82. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +9 -0
  83. solace_agent_mesh/gateway/http_sse/api/controllers/session_controller.py +355 -0
  84. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +279 -0
  85. solace_agent_mesh/gateway/http_sse/api/controllers/user_controller.py +35 -0
  86. solace_agent_mesh/gateway/http_sse/api/dto/__init__.py +10 -0
  87. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +37 -0
  88. solace_agent_mesh/gateway/http_sse/api/dto/requests/session_requests.py +49 -0
  89. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +66 -0
  90. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +43 -0
  91. solace_agent_mesh/gateway/http_sse/api/dto/responses/session_responses.py +68 -0
  92. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +74 -0
  93. solace_agent_mesh/gateway/http_sse/app.py +31 -1
  94. solace_agent_mesh/gateway/http_sse/application/__init__.py +3 -0
  95. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +3 -0
  96. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +135 -0
  97. solace_agent_mesh/gateway/http_sse/component.py +224 -62
  98. solace_agent_mesh/gateway/http_sse/dependencies.py +142 -39
  99. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +3 -0
  100. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +90 -0
  101. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +3 -0
  102. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +54 -0
  103. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +4 -0
  104. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +3 -0
  105. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +123 -0
  106. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +4 -0
  107. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +16 -0
  108. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +119 -0
  109. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +31 -0
  110. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +12 -0
  111. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +3 -0
  112. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +174 -0
  113. solace_agent_mesh/gateway/http_sse/main.py +289 -85
  114. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +121 -54
  115. solace_agent_mesh/gateway/http_sse/routers/config.py +3 -1
  116. solace_agent_mesh/gateway/http_sse/routers/tasks.py +83 -2
  117. solace_agent_mesh/gateway/http_sse/routers/visualization.py +7 -7
  118. solace_agent_mesh/gateway/http_sse/session_manager.py +64 -30
  119. solace_agent_mesh/gateway/http_sse/shared/__init__.py +9 -0
  120. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  121. solace_agent_mesh/gateway/http_sse/shared/enums.py +45 -0
  122. solace_agent_mesh/gateway/http_sse/shared/types.py +45 -0
  123. solace_agent_mesh/templates/shared_config.yaml +4 -5
  124. solace_agent_mesh/templates/webui.yaml +8 -10
  125. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/METADATA +5 -3
  126. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/RECORD +130 -91
  127. solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +0 -1
  128. solace_agent_mesh/assets/docs/search-doc-1756992446316.json +0 -1
  129. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-BmF2l6vg.js +0 -1
  130. solace_agent_mesh/client/webui/frontend/static/assets/client-D881Dttc.js +0 -49
  131. solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +0 -699
  132. solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +0 -1
  133. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-Bym6YkMd.js +0 -98
  134. solace_agent_mesh/gateway/http_sse/routers/sessions.py +0 -85
  135. solace_agent_mesh/gateway/http_sse/routers/users.py +0 -59
  136. /solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js.LICENSE.txt → main.08d30374.js.LICENSE.txt} +0 -0
  137. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/WHEEL +0 -0
  138. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/entry_points.txt +0 -0
  139. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,21 @@
1
- import click
1
+ import re
2
2
  from pathlib import Path
3
+
4
+ import click
3
5
  import yaml
4
- import re
5
- from ...utils import ask_if_not_provided, load_template, get_formatted_names
6
+
6
7
  from config_portal.backend.common import DEFAULT_COMMUNICATION_TIMEOUT
7
8
 
9
+ from ...utils import ask_if_not_provided, get_formatted_names, load_template
10
+
8
11
  ORCHESTRATOR_DEFAULTS = {
9
12
  "agent_name": "OrchestratorAgent",
10
13
  "supports_streaming": True,
11
14
  "artifact_handling_mode": "reference",
12
15
  "enable_embed_resolution": True,
13
16
  "enable_artifact_content_instruction": True,
14
- "session_service": {"type": "memory", "default_behavior": "PERSISTENT"},
17
+ "enable_builtin_artifact_tools": {"enabled": True},
18
+ "enable_builtin_data_tools": {"enabled": True},
15
19
  "artifact_service": {
16
20
  "type": "filesystem",
17
21
  "base_path": "/tmp/samv2",
@@ -83,16 +87,16 @@ def create_orchestrator_config(
83
87
  options,
84
88
  "session_service_type",
85
89
  "Enter session service type",
86
- ORCHESTRATOR_DEFAULTS["session_service"]["type"],
90
+ "memory",
87
91
  skip_interactive,
88
- choices=["memory", "vertex_rag"],
92
+ choices=["sql", "memory", "vertex_rag"],
89
93
  )
90
94
 
91
95
  session_behavior = ask_if_not_provided(
92
96
  options,
93
97
  "session_service_behavior",
94
98
  "Enter session service behavior",
95
- ORCHESTRATOR_DEFAULTS["session_service"]["default_behavior"],
99
+ "PERSISTENT",
96
100
  skip_interactive,
97
101
  choices=["PERSISTENT", "RUN_BASED"],
98
102
  )
@@ -110,7 +114,7 @@ def create_orchestrator_config(
110
114
  s3_bucket_name = None
111
115
  s3_endpoint_url = None
112
116
  s3_region = None
113
-
117
+
114
118
  if artifact_type == "filesystem":
115
119
  artifact_base_path = ask_if_not_provided(
116
120
  options,
@@ -127,7 +131,7 @@ def create_orchestrator_config(
127
131
  options["s3_endpoint_url"] = options["artifact_service_endpoint_url"]
128
132
  if options.get("artifact_service_region"):
129
133
  options["s3_region"] = options["artifact_service_region"]
130
-
134
+
131
135
  s3_bucket_name = ask_if_not_provided(
132
136
  options,
133
137
  "s3_bucket_name",
@@ -304,18 +308,13 @@ def create_orchestrator_config(
304
308
  if artifact_type == "filesystem":
305
309
  artifact_base_path_line = f'base_path: "{artifact_base_path}"'
306
310
  elif artifact_type == "s3":
307
- s3_config_lines = [f'bucket_name: "{s3_bucket_name}"']
308
- if s3_endpoint_url:
309
- s3_config_lines.append(f'endpoint_url: "{s3_endpoint_url}"')
310
- if s3_region:
311
- s3_config_lines.append(f'region: "{s3_region}"')
311
+ s3_config_lines = ["bucket_name: ${S3_BUCKET_NAME}"]
312
+ s3_config_lines.append("endpoint_url: ${S3_ENDPOINT_URL:-}")
313
+ s3_config_lines.append("region: ${S3_REGION}")
312
314
  artifact_base_path_line = "\n ".join(s3_config_lines)
313
315
 
314
316
  shared_replacements = {
315
- "__DEFAULT_SESSION_SERVICE_TYPE__": session_type,
316
- "__DEFAULT_SESSION_SERVICE_BEHAVIOR__": session_behavior,
317
317
  "__DEFAULT_ARTIFACT_SERVICE_TYPE__": artifact_type,
318
- "__DEFAULT_ARTIFACT_SERVICE_BASE_PATH_LINE__": artifact_base_path_line,
319
318
  "__DEFAULT_ARTIFACT_SERVICE_SCOPE__": artifact_scope,
320
319
  }
321
320
 
@@ -325,6 +324,18 @@ def create_orchestrator_config(
325
324
  placeholder, str(value)
326
325
  )
327
326
 
327
+ if not artifact_base_path_line:
328
+ modified_shared_content = re.sub(
329
+ r"\s*# __DEFAULT_ARTIFACT_SERVICE_BASE_PATH_LINE__.*",
330
+ "",
331
+ modified_shared_content,
332
+ )
333
+ else:
334
+ modified_shared_content = modified_shared_content.replace(
335
+ " # __DEFAULT_ARTIFACT_SERVICE_BASE_PATH_LINE__",
336
+ f" {artifact_base_path_line}",
337
+ )
338
+
328
339
  shared_config_dest_path.parent.mkdir(parents=True, exist_ok=True)
329
340
  with open(shared_config_dest_path, "w", encoding="utf-8") as f:
330
341
  f.write(modified_shared_content)
@@ -366,8 +377,8 @@ def create_orchestrator_config(
366
377
  try:
367
378
  orchestrator_template_content = load_template("main_orchestrator.yaml")
368
379
 
369
- session_service_block_for_orchestrator = "*default_session_service"
370
- artifact_service_block_for_orchestrator = "*default_artifact_service"
380
+ formatted_name = get_formatted_names(options["agent_name"])
381
+ kebab_case_name = formatted_name.get("KEBAB_CASE_NAME")
371
382
 
372
383
  deny_list_line = ""
373
384
  if deny_list:
@@ -376,7 +387,7 @@ def create_orchestrator_config(
376
387
  .strip()
377
388
  .replace("'", '"')
378
389
  )
379
- deny_list_line = f"deny_list: {deny_list_yaml}\n "
390
+ deny_list_line = f"deny_list: {deny_list_yaml}"
380
391
 
381
392
  default_instruction = """You are the Orchestrator Agent within an AI agentic system. Your primary responsibilities are to:
382
393
  1. Process tasks received from external sources via the system Gateway.
@@ -390,8 +401,40 @@ def create_orchestrator_config(
390
401
  - You must then review the list of artifacts and return the ones that are important for the user by using the `signal_artifact_for_return` tool.
391
402
  - Provide regular progress updates using `status_update` embed directives, especially before initiating any tool call."""
392
403
 
393
- formatted_name = get_formatted_names(options["agent_name"])
394
- kebab_case_name = formatted_name.get("KEBAB_CASE_NAME")
404
+ if session_type == "sql":
405
+ session_service_lines = [
406
+ f'type: "{session_type}"',
407
+ 'database_url: "${ORCHESTRATOR_DATABASE_URL}"',
408
+ f'default_behavior: "{session_behavior}"',
409
+ ]
410
+ session_service_block = "\n" + "\n".join(
411
+ [f" {line}" for line in session_service_lines]
412
+ )
413
+
414
+ data_dir = project_root / "data"
415
+ data_dir.mkdir(exist_ok=True)
416
+ orchestrator_db_file = data_dir / "orchestrator.db"
417
+ orchestrator_database_url = f"sqlite:///{orchestrator_db_file.resolve()}"
418
+
419
+ try:
420
+ env_path = project_root / ".env"
421
+ with open(env_path, "a", encoding="utf-8") as f:
422
+ f.write(
423
+ f'\nORCHESTRATOR_DATABASE_URL="{orchestrator_database_url}"\n'
424
+ )
425
+ click.echo(
426
+ f" Added ORCHESTRATOR_DATABASE_URL to .env: {orchestrator_database_url}"
427
+ )
428
+ except Exception as e:
429
+ click.echo(
430
+ click.style(
431
+ f"Warning: Could not add ORCHESTRATOR_DATABASE_URL to .env: {e}",
432
+ fg="yellow",
433
+ ),
434
+ err=True,
435
+ )
436
+ else:
437
+ session_service_block = "*default_session_service"
395
438
 
396
439
  orchestrator_replacements = {
397
440
  "__NAMESPACE__": "${NAMESPACE}",
@@ -400,8 +443,8 @@ def create_orchestrator_config(
400
443
  "__AGENT_NAME__": options["agent_name"],
401
444
  "__LOG_FILE_NAME__": f"{kebab_case_name}.log",
402
445
  "__INSTRUCTION__": default_instruction,
403
- "__SESSION_SERVICE__": session_service_block_for_orchestrator,
404
- "__ARTIFACT_SERVICE__": artifact_service_block_for_orchestrator,
446
+ "__SESSION_SERVICE__": session_service_block,
447
+ "__ARTIFACT_SERVICE__": "*default_artifact_service",
405
448
  "__ARTIFACT_HANDLING_MODE__": artifact_handling_mode,
406
449
  "__ENABLE_EMBED_RESOLUTION__": str(enable_embed_resolution).lower(),
407
450
  "__ENABLE_ARTIFACT_CONTENT_INSTRUCTION__": str(
@@ -425,7 +468,6 @@ def create_orchestrator_config(
425
468
  )
426
469
  .strip()
427
470
  .replace("'", '"'),
428
- "__INTER_AGENT_COMMUNICATION_DENY_LIST_LINE__": deny_list_line,
429
471
  "__INTER_AGENT_COMMUNICATION_TIMEOUT__": str(
430
472
  inter_agent_communication_timeout
431
473
  ),
@@ -437,6 +479,19 @@ def create_orchestrator_config(
437
479
  placeholder, str(value)
438
480
  )
439
481
 
482
+ if deny_list:
483
+ modified_orchestrator_content = modified_orchestrator_content.replace(
484
+ "__INTER_AGENT_COMMUNICATION_DENY_LIST_LINE__",
485
+ deny_list_line,
486
+ )
487
+ else:
488
+ modified_orchestrator_content = re.sub(
489
+ r"^\s*__INTER_AGENT_COMMUNICATION_DENY_LIST_LINE__\n?$",
490
+ "",
491
+ modified_orchestrator_content,
492
+ flags=re.MULTILINE,
493
+ )
494
+
440
495
  main_orchestrator_path.parent.mkdir(parents=True, exist_ok=True)
441
496
  with open(main_orchestrator_path, "w", encoding="utf-8") as f:
442
497
  f.write(modified_orchestrator_content)
@@ -1,9 +1,10 @@
1
- import click
2
1
  import multiprocessing
3
2
  import sys
4
3
  import webbrowser
5
- from ...utils import wait_for_server
6
4
 
5
+ import click
6
+
7
+ from ...utils import wait_for_server
7
8
 
8
9
  try:
9
10
  from config_portal.backend.server import run_flask
@@ -19,7 +20,6 @@ except ImportError as e:
19
20
  sys.exit(1)
20
21
 
21
22
 
22
-
23
23
  def perform_web_init(current_cli_params: dict) -> dict:
24
24
  """
25
25
  Launches the web-based configuration portal and updates params.
@@ -55,7 +55,7 @@ def perform_web_init(current_cli_params: dict) -> dict:
55
55
  else:
56
56
  click.echo(
57
57
  click.style(
58
- f"Server did not start in time. Please check for errors and try again.",
58
+ "Server did not start in time. Please check for errors and try again.",
59
59
  fg="red",
60
60
  )
61
61
  )
@@ -67,12 +67,34 @@ def perform_web_init(current_cli_params: dict) -> dict:
67
67
  init_gui_process.join()
68
68
  if shared_config_from_web:
69
69
  config_from_portal = dict(shared_config_from_web)
70
- config_from_portal["llm_planning_model_name"] = config_from_portal.get(
71
- "llm_planning_model_name"
72
- ) or config_from_portal.get("llm_model_name")
73
- config_from_portal["llm_general_model_name"] = config_from_portal.get(
74
- "llm_general_model_name"
75
- ) or config_from_portal.get("llm_model_name")
70
+
71
+ # Map web portal keys to CLI expected keys for backwards compatibility
72
+ key_mappings = {
73
+ "llm_api_key": "llm_service_api_key",
74
+ "llm_endpoint_url": "llm_service_endpoint",
75
+ "llm_model_name": "llm_service_model_name",
76
+ }
77
+
78
+ for old_key, new_key in key_mappings.items():
79
+ if old_key in config_from_portal and new_key not in config_from_portal:
80
+ config_from_portal[new_key] = config_from_portal[old_key]
81
+
82
+ # Handle planning and general model names with fallback to single model name
83
+ config_from_portal["llm_service_planning_model_name"] = (
84
+ config_from_portal.get("llm_service_planning_model_name")
85
+ or config_from_portal.get("llm_planning_model_name")
86
+ or config_from_portal.get("llm_model_name")
87
+ )
88
+
89
+ config_from_portal["llm_service_general_model_name"] = (
90
+ config_from_portal.get("llm_service_general_model_name")
91
+ or config_from_portal.get("llm_general_model_name")
92
+ or config_from_portal.get("llm_model_name")
93
+ )
94
+
95
+ # Clean up deprecated keys if new keys are present
96
+ config_from_portal.pop("llm_planning_model_name", None)
97
+ config_from_portal.pop("llm_general_model_name", None)
76
98
 
77
99
  click.echo(
78
100
  click.style("Configuration received from web portal.", fg="green")
@@ -3,14 +3,15 @@ from pathlib import Path
3
3
 
4
4
  from ...utils import ask_if_not_provided, ask_yes_no_question, load_template
5
5
 
6
+
6
7
  WEBUI_GATEWAY_DEFAULTS = {
7
8
  "webui_frontend_welcome_message": "",
8
9
  "webui_frontend_bot_name": "Solace Agent Mesh",
9
10
  "webui_frontend_collect_feedback": False,
10
11
  "webui_session_secret_key": "please_change_me_in",
11
12
  "webui_fastapi_host": "127.0.0.1",
12
- "webui_fastapi_port": 8000, # Store as int if CLI option is int
13
- "webui_fastapi_https_port": 8443, # Store as int if CLI option is int
13
+ "webui_fastapi_port": 8000,
14
+ "webui_fastapi_https_port": 8443,
14
15
  "webui_ssl_keyfile": "",
15
16
  "webui_ssl_certfile": "",
16
17
  "webui_ssl_keyfile_password": "",
@@ -39,6 +40,10 @@ def create_webui_gateway_config(
39
40
 
40
41
  options["add_webui_gateway"] = add_gateway
41
42
 
43
+ if not add_gateway:
44
+ click.echo(click.style(" Skipping Web UI Gateway file creation.", fg="yellow"))
45
+ return True
46
+
42
47
  options["webui_session_secret_key"] = ask_if_not_provided(
43
48
  options,
44
49
  "webui_session_secret_key",
@@ -69,9 +74,11 @@ def create_webui_gateway_config(
69
74
  none_interactive=skip_interactive,
70
75
  )
71
76
  options["webui_fastapi_https_port"] = ask_if_not_provided(
72
- options, "webui_fastapi_https_port", "Enter Web UI FastAPI HTTPS Port",
77
+ options,
78
+ "webui_fastapi_https_port",
79
+ "Enter Web UI FastAPI HTTPS Port",
73
80
  default=default_values.get("webui_fastapi_https_port", 8443),
74
- none_interactive=skip_interactive
81
+ none_interactive=skip_interactive,
75
82
  )
76
83
  options["webui_enable_embed_resolution"] = ask_if_not_provided(
77
84
  options,
@@ -85,19 +92,26 @@ def create_webui_gateway_config(
85
92
  is_bool=True,
86
93
  )
87
94
  options["webui_ssl_keyfile"] = ask_if_not_provided(
88
- options, "webui_ssl_keyfile", "Enter SSL Key File Path",
95
+ options,
96
+ "webui_ssl_keyfile",
97
+ "Enter SSL Key File Path",
89
98
  default=default_values.get("webui_ssl_keyfile", ""),
90
- none_interactive=skip_interactive
99
+ none_interactive=skip_interactive,
91
100
  )
92
101
  options["webui_ssl_certfile"] = ask_if_not_provided(
93
- options, "webui_ssl_certfile", "Enter SSL Certificate File Path",
102
+ options,
103
+ "webui_ssl_certfile",
104
+ "Enter SSL Certificate File Path",
94
105
  default=default_values.get("webui_ssl_certfile", ""),
95
- none_interactive=skip_interactive
106
+ none_interactive=skip_interactive,
96
107
  )
97
108
  options["webui_ssl_keyfile_password"] = ask_if_not_provided(
98
- options, "webui_ssl_keyfile_password", "Enter SSL Key File Passphrase",
109
+ options,
110
+ "webui_ssl_keyfile_password",
111
+ "Enter SSL Key File Passphrase",
99
112
  default=default_values.get("webui_ssl_keyfile_password", ""),
100
- none_interactive=skip_interactive, hide_input=True
113
+ none_interactive=skip_interactive,
114
+ hide_input=True,
101
115
  )
102
116
 
103
117
  options["webui_frontend_welcome_message"] = ask_if_not_provided(
@@ -131,15 +145,58 @@ def create_webui_gateway_config(
131
145
  is_bool=True,
132
146
  )
133
147
 
134
- if not add_gateway:
135
- click.echo(click.style(" Skipping Web UI Gateway file creation.", fg="yellow"))
136
- return True
148
+ session_type = ask_if_not_provided(
149
+ options,
150
+ "webui_session_service_type",
151
+ "Enter WebUI session service type",
152
+ "sql",
153
+ skip_interactive,
154
+ choices=["sql", "memory"],
155
+ )
156
+
157
+ session_behavior = ask_if_not_provided(
158
+ options,
159
+ "webui_session_service_behavior",
160
+ "Enter WebUI session service behavior",
161
+ "PERSISTENT",
162
+ skip_interactive,
163
+ choices=["PERSISTENT", "RUN_BASED"],
164
+ )
137
165
 
138
166
  click.echo("Creating Web UI Gateway configuration file...")
139
167
  destination_path = project_root / "configs" / "gateways" / "webui.yaml"
140
168
 
141
169
  try:
142
170
  template_content = load_template("webui.yaml")
171
+
172
+ if session_type == "sql":
173
+ session_service_lines = [
174
+ f'type: "{session_type}"',
175
+ f'database_url: "${{WEB_UI_GATEWAY_DATABASE_URL}}"',
176
+ f'default_behavior: "{session_behavior}"',
177
+ ]
178
+ session_service_block = "\n" + "\n".join(
179
+ [f" {line}" for line in session_service_lines]
180
+ )
181
+
182
+ data_dir = project_root / "data"
183
+ data_dir.mkdir(exist_ok=True)
184
+ webui_db_file = data_dir / "webui_gateway.db"
185
+ webui_database_url = f"sqlite:///{webui_db_file.resolve()}"
186
+
187
+ try:
188
+ env_path = project_root / ".env"
189
+ with open(env_path, "a", encoding="utf-8") as f:
190
+ f.write(f'\nWEB_UI_GATEWAY_DATABASE_URL="{webui_database_url}"\n')
191
+ click.echo(f" Added WEB_UI_GATEWAY_DATABASE_URL to .env: {webui_database_url}")
192
+ except Exception as e:
193
+ click.echo(
194
+ click.style(f"Warning: Could not add WEB_UI_GATEWAY_DATABASE_URL to .env: {e}", fg="yellow"),
195
+ err=True,
196
+ )
197
+ else:
198
+ session_service_block = "*default_session_service"
199
+
143
200
  replacements = {
144
201
  "__FRONTEND_WELCOME_MESSAGE__": str(
145
202
  options.get("webui_frontend_welcome_message", "")
@@ -150,11 +207,13 @@ def create_webui_gateway_config(
150
207
  "__FRONTEND_COLLECT_FEEDBACK__": str(
151
208
  options.get("webui_frontend_collect_feedback", False)
152
209
  ).lower(),
210
+ "__SESSION_SERVICE__": session_service_block,
153
211
  }
154
212
 
155
213
  modified_content = template_content
156
214
  for placeholder, value in replacements.items():
157
- modified_content = modified_content.replace(placeholder, value)
215
+ if value is not None:
216
+ modified_content = modified_content.replace(placeholder, str(value))
158
217
 
159
218
  destination_path.parent.mkdir(parents=True, exist_ok=True)
160
219
  with open(destination_path, "w", encoding="utf-8") as f:
@@ -164,7 +223,7 @@ def create_webui_gateway_config(
164
223
  return True
165
224
 
166
225
  except FileNotFoundError:
167
- click.echo(click.style(f"Error: Template file not found.", fg="red"), err=True)
226
+ click.echo(click.style("Error: Template file not found.", fg="red"), err=True)
168
227
  return False
169
228
  except IOError as e:
170
229
  click.echo(
@@ -18,12 +18,10 @@ DEFAULT_PLUGIN_VERSION = "0.1.0"
18
18
 
19
19
 
20
20
  def ensure_directory_exists(path: pathlib.Path):
21
- """Creates a directory if it doesn't exist."""
22
21
  path.mkdir(parents=True, exist_ok=True)
23
22
 
24
23
 
25
24
  def replace_placeholders(content: str, replacements: dict) -> str:
26
- """Replaces placeholders in a string."""
27
25
  for placeholder, value in replacements.items():
28
26
  content = content.replace(placeholder, str(value))
29
27
  return content
@@ -1,8 +1,10 @@
1
- import click
2
1
  import os
3
2
  import sys
4
3
  from pathlib import Path
5
- from dotenv import load_dotenv, find_dotenv
4
+
5
+ import click
6
+ from dotenv import find_dotenv, load_dotenv
7
+
6
8
  from cli.utils import error_exit
7
9
  from solace_agent_mesh.common.utils.initializer import initialize
8
10
 
@@ -110,7 +112,7 @@ def run(files: tuple[str, ...], skip_files: tuple[str, ...], system_env: bool):
110
112
  ),
111
113
  err=True,
112
114
  )
113
- return 1
115
+ sys.exit(1)
114
116
 
115
117
  for filepath in configs_dir.rglob("*.yaml"):
116
118
  if filepath.name.startswith("_") or filepath.name.startswith(
@@ -1,10 +1,12 @@
1
+ import importlib
1
2
  import os
3
+ import re
2
4
  from pathlib import Path
3
- import importlib
5
+ from time import sleep
6
+
4
7
  import click
5
- import re
6
8
  import requests
7
- from time import sleep
9
+ from sqlalchemy import create_engine, event
8
10
 
9
11
 
10
12
  def ask_yes_no_question(question: str, default=False) -> bool:
@@ -83,7 +85,7 @@ def load_template(name, parser=None, *args):
83
85
  if not os.path.exists(template_file):
84
86
  raise FileNotFoundError(f"Template file '{template_file}' does not exist.")
85
87
 
86
- with open(template_file, "r", encoding="utf-8") as f:
88
+ with open(template_file, encoding="utf-8") as f:
87
89
  if parser:
88
90
  file = parser(f.read(), *args)
89
91
  else:
@@ -94,21 +96,23 @@ def load_template(name, parser=None, *args):
94
96
 
95
97
  def get_formatted_names(name: str):
96
98
  # Normalize separators
97
- normalized = re.sub(r'[\s\-_]+', '_', name.strip())
99
+ normalized = re.sub(r"[\s\-_]+", "_", name.strip())
98
100
 
99
- camel_case_split = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', normalized) # fooBar -> foo_Bar
100
- acronym_split = re.sub(r'([A-Z]+)([A-Z][a-z])', r'\1_\2', camel_case_split) # APIKey -> API_Key
101
+ camel_case_split = re.sub(
102
+ r"([a-z0-9])([A-Z])", r"\1_\2", normalized
103
+ ) # fooBar -> foo_Bar
104
+ acronym_split = re.sub(
105
+ r"([A-Z]+)([A-Z][a-z])", r"\1_\2", camel_case_split
106
+ ) # APIKey -> API_Key
101
107
 
102
- raw_parts = [p for p in acronym_split.split('_') if p]
108
+ raw_parts = [p for p in acronym_split.split("_") if p]
103
109
 
104
110
  parts = [p.lower() for p in raw_parts]
105
111
 
106
112
  # Spaced capitalized name:
107
113
  # - If original was all caps, keep it all caps (API -> API)
108
114
  # - Else capitalize normally
109
- spaced_capitalized_parts = [
110
- p if p.isupper() else p.capitalize() for p in raw_parts
111
- ]
115
+ spaced_capitalized_parts = [p if p.isupper() else p.capitalize() for p in raw_parts]
112
116
 
113
117
  return {
114
118
  "KEBAB_CASE_NAME": "-".join(parts),
@@ -192,6 +196,7 @@ def indent_multiline_string(
192
196
  else:
193
197
  return "\n".join(indentation + line for line in text.splitlines()).lstrip()
194
198
 
199
+
195
200
  def wait_for_server(url, timeout=30):
196
201
  start = 0
197
202
  while start < timeout:
@@ -203,4 +208,55 @@ def wait_for_server(url, timeout=30):
203
208
  pass
204
209
  sleep(0.5)
205
210
  start += 0.5
206
- return False
211
+ return False
212
+
213
+
214
+ def create_and_validate_database(database_url: str, db_name: str = "database") -> bool:
215
+ """
216
+ Create and validate a database connection.
217
+
218
+ Args:
219
+ database_url (str): Database URL to validate
220
+ db_name (str): Descriptive name for logging purposes
221
+
222
+ Returns:
223
+ bool: True if successful, raises exception if failed
224
+ """
225
+ try:
226
+ # Handle SQLite file creation
227
+ if database_url.startswith("sqlite:///"):
228
+ db_file_path_str = database_url.replace("sqlite:///", "")
229
+ db_file_path = Path(db_file_path_str)
230
+ db_file_path.parent.mkdir(parents=True, exist_ok=True)
231
+
232
+ engine = create_engine(database_url)
233
+
234
+ @event.listens_for(engine, "connect")
235
+ def set_sqlite_pragma(dbapi_connection, connection_record):
236
+ cursor = dbapi_connection.cursor()
237
+ cursor.execute("PRAGMA foreign_keys=ON")
238
+ cursor.close()
239
+ elif database_url.startswith("postgresql"):
240
+ # Check if PostgreSQL driver is available
241
+ try:
242
+ import psycopg2
243
+ except ImportError:
244
+ raise ImportError(
245
+ "PostgreSQL support requires psycopg2. Install with: "
246
+ "pip install 'solace-agent-mesh[postgresql]'"
247
+ )
248
+ engine = create_engine(database_url)
249
+ else:
250
+ engine = create_engine(database_url)
251
+
252
+ with engine.connect() as connection:
253
+ pass
254
+
255
+ engine.dispose()
256
+ click.echo(click.style(f" {db_name} validation successful.", fg="green"))
257
+ return True
258
+
259
+ except Exception as e:
260
+ error_msg = f"Database connection failed for {db_name}: {e}"
261
+ click.echo(click.style(f" Error: {error_msg}", fg="red"), err=True)
262
+ raise ValueError(error_msg)
@@ -0,0 +1 @@
1
+ import{c as n}from"./client-BeBkzgWW.js";import{r as a,j as s}from"./vendor-CE0AeXyK.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,{}));