solace-agent-mesh 1.1.0__py3-none-any.whl → 1.3.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 (168) 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/adk/setup.py +141 -34
  4. solace_agent_mesh/agent/protocol/event_handlers.py +27 -21
  5. solace_agent_mesh/agent/sac/app.py +0 -1
  6. solace_agent_mesh/agent/sac/component.py +0 -1
  7. solace_agent_mesh/agent/tools/__init__.py +1 -0
  8. solace_agent_mesh/agent/tools/dynamic_tool.py +362 -0
  9. solace_agent_mesh/assets/docs/404.html +3 -3
  10. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.d97b8e94.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/483cef9a.4e972867.js +1 -0
  12. solace_agent_mesh/assets/docs/assets/js/55f47984.cf3781c4.js +1 -0
  13. solace_agent_mesh/assets/docs/assets/js/664b740a.1b744a32.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/75384d09.c193a8f0.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/9a09e75d.d6607c56.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/aba87c2f.071e2d94.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/ae0e903d.4d8dda10.js +1 -0
  18. solace_agent_mesh/assets/docs/assets/js/c835a94d.146e3186.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/f284c35a.7334119c.js +1 -0
  20. solace_agent_mesh/assets/docs/assets/js/main.1c79039d.js +2 -0
  21. solace_agent_mesh/assets/docs/assets/js/runtime~main.858117b7.js +1 -0
  22. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +29 -0
  23. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +25 -0
  24. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html } +6 -6
  25. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html } +6 -6
  26. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +19 -27
  27. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  29. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  30. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
  37. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +63 -0
  57. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  60. solace_agent_mesh/assets/docs/lunr-index-1757531604543.json +1 -0
  61. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  62. solace_agent_mesh/assets/docs/search-doc-1757531604543.json +1 -0
  63. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  64. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  65. solace_agent_mesh/cli/__init__.py +1 -1
  66. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +125 -48
  67. solace_agent_mesh/cli/commands/eval_cmd.py +14 -0
  68. solace_agent_mesh/cli/commands/init_cmd/__init__.py +53 -31
  69. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  70. solace_agent_mesh/cli/commands/init_cmd/env_step.py +19 -8
  71. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +80 -25
  72. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +32 -10
  73. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +74 -15
  74. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +0 -2
  75. solace_agent_mesh/cli/commands/run_cmd.py +5 -3
  76. solace_agent_mesh/cli/utils.py +68 -12
  77. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-CAX9u8a7.js +1 -0
  78. solace_agent_mesh/client/webui/frontend/static/assets/client-DXU9SPI5.js +25 -0
  79. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +1 -0
  80. solace_agent_mesh/client/webui/frontend/static/assets/main-C1k9E0aC.js +339 -0
  81. solace_agent_mesh/client/webui/frontend/static/assets/vendor-B0BEKoAR.js +390 -0
  82. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -2
  83. solace_agent_mesh/client/webui/frontend/static/index.html +4 -3
  84. solace_agent_mesh/common/utils/embeds/resolver.py +1 -0
  85. solace_agent_mesh/config_portal/backend/common.py +2 -2
  86. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-bFMKlzKf.js +98 -0
  87. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d845808d.js → manifest-89db7c30.js} +1 -1
  88. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  89. solace_agent_mesh/evaluation/message_organizer.py +35 -56
  90. solace_agent_mesh/evaluation/run.py +26 -5
  91. solace_agent_mesh/evaluation/subscriber.py +35 -10
  92. solace_agent_mesh/evaluation/summary_builder.py +27 -34
  93. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +676 -0
  94. solace_agent_mesh/gateway/http_sse/alembic/env.py +85 -0
  95. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  96. solace_agent_mesh/gateway/http_sse/alembic/versions/b1c2d3e4f5g6_add_database_indexes.py +83 -0
  97. solace_agent_mesh/gateway/http_sse/alembic/versions/d5b3f8f2e9a0_create_initial_database.py +58 -0
  98. solace_agent_mesh/gateway/http_sse/alembic.ini +147 -0
  99. solace_agent_mesh/gateway/http_sse/api/__init__.py +11 -0
  100. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +9 -0
  101. solace_agent_mesh/gateway/http_sse/api/controllers/session_controller.py +355 -0
  102. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +279 -0
  103. solace_agent_mesh/gateway/http_sse/api/controllers/user_controller.py +35 -0
  104. solace_agent_mesh/gateway/http_sse/api/dto/__init__.py +10 -0
  105. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +37 -0
  106. solace_agent_mesh/gateway/http_sse/api/dto/requests/session_requests.py +49 -0
  107. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +66 -0
  108. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +43 -0
  109. solace_agent_mesh/gateway/http_sse/api/dto/responses/session_responses.py +68 -0
  110. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +74 -0
  111. solace_agent_mesh/gateway/http_sse/app.py +31 -1
  112. solace_agent_mesh/gateway/http_sse/application/__init__.py +3 -0
  113. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +3 -0
  114. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +135 -0
  115. solace_agent_mesh/gateway/http_sse/component.py +224 -62
  116. solace_agent_mesh/gateway/http_sse/dependencies.py +148 -45
  117. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +3 -0
  118. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +90 -0
  119. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +3 -0
  120. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +54 -0
  121. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +4 -0
  122. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +3 -0
  123. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +123 -0
  124. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +4 -0
  125. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +16 -0
  126. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +119 -0
  127. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +31 -0
  128. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +12 -0
  129. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +3 -0
  130. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +174 -0
  131. solace_agent_mesh/gateway/http_sse/main.py +291 -87
  132. solace_agent_mesh/gateway/http_sse/routers/{agents.py → agent_cards.py} +7 -7
  133. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +121 -54
  134. solace_agent_mesh/gateway/http_sse/routers/config.py +3 -1
  135. solace_agent_mesh/gateway/http_sse/routers/tasks.py +83 -2
  136. solace_agent_mesh/gateway/http_sse/routers/visualization.py +7 -7
  137. solace_agent_mesh/gateway/http_sse/services/{agent_service.py → agent_card_service.py} +19 -19
  138. solace_agent_mesh/gateway/http_sse/session_manager.py +64 -30
  139. solace_agent_mesh/gateway/http_sse/shared/__init__.py +9 -0
  140. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  141. solace_agent_mesh/gateway/http_sse/shared/enums.py +45 -0
  142. solace_agent_mesh/gateway/http_sse/shared/types.py +45 -0
  143. solace_agent_mesh/templates/shared_config.yaml +4 -5
  144. solace_agent_mesh/templates/webui.yaml +8 -10
  145. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/METADATA +5 -3
  146. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/RECORD +150 -104
  147. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.8ccb9901.js +0 -1
  148. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +0 -1
  149. solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +0 -1
  150. solace_agent_mesh/assets/docs/assets/js/75384d09.bf78fbdb.js +0 -1
  151. solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +0 -1
  152. solace_agent_mesh/assets/docs/assets/js/aba87c2f.76376d7c.js +0 -1
  153. solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +0 -1
  154. solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +0 -2
  155. solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +0 -1
  156. solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +0 -1
  157. solace_agent_mesh/assets/docs/search-doc-1756992446316.json +0 -1
  158. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-BmF2l6vg.js +0 -1
  159. solace_agent_mesh/client/webui/frontend/static/assets/client-D881Dttc.js +0 -49
  160. solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +0 -699
  161. solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +0 -1
  162. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-Bym6YkMd.js +0 -98
  163. solace_agent_mesh/gateway/http_sse/routers/sessions.py +0 -85
  164. solace_agent_mesh/gateway/http_sse/routers/users.py +0 -59
  165. /solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js.LICENSE.txt → main.1c79039d.js.LICENSE.txt} +0 -0
  166. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/WHEEL +0 -0
  167. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/entry_points.txt +0 -0
  168. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,64 +1,118 @@
1
- """
2
- Defines the FastAPI application instance, mounts routers, and configures middleware.
3
- """
4
-
5
- from fastapi import (
6
- FastAPI,
7
- Request as FastAPIRequest,
8
- HTTPException,
9
- status,
10
- )
11
- from fastapi.responses import JSONResponse
1
+ import os
2
+ from pathlib import Path
3
+ from typing import TYPE_CHECKING
4
+
5
+ import httpx
6
+ import sqlalchemy as sa
7
+ from a2a.types import InternalError, JSONRPCError
8
+ from a2a.types import JSONRPCResponse as A2AJSONRPCResponse
9
+ from alembic import command
10
+ from alembic.config import Config
11
+ from fastapi import FastAPI, HTTPException, status
12
+ from fastapi import Request as FastAPIRequest
12
13
  from fastapi.exceptions import RequestValidationError
13
14
  from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import JSONResponse
16
+ from solace_ai_connector.common.log import log
14
17
  from starlette.middleware.sessions import SessionMiddleware
15
18
  from starlette.staticfiles import StaticFiles
16
- import os
17
- from pathlib import Path
18
- import httpx
19
19
 
20
- from solace_ai_connector.common.log import log
20
+ from ...common import a2a
21
+ from ...gateway.http_sse import dependencies
21
22
  from ...gateway.http_sse.routers import (
22
- agents,
23
- tasks,
24
- sse,
25
- config,
23
+ agent_cards,
26
24
  artifacts,
27
- visualization,
28
- sessions,
29
- people,
30
25
  auth,
31
- users,
32
- )
33
-
34
- from ...gateway.http_sse import dependencies
35
- from a2a.types import (
36
- JSONRPCError,
37
- InternalError,
26
+ config,
27
+ people,
28
+ sse,
29
+ tasks,
30
+ visualization,
38
31
  )
39
- from ...common import a2a
40
32
 
41
- from typing import TYPE_CHECKING
33
+ # Import persistence-aware controllers
34
+ from .api.controllers.session_controller import router as session_router
35
+ from .api.controllers.task_controller import router as task_router
36
+ from .api.controllers.user_controller import router as user_router
37
+ from .infrastructure.persistence.database_service import DatabaseService
42
38
 
43
39
  if TYPE_CHECKING:
44
40
  from gateway.http_sse.component import WebUIBackendComponent
45
41
 
46
42
  app = FastAPI(
47
43
  title="A2A Web UI Backend",
48
- version="0.1.0",
44
+ version="1.0.0", # Updated to reflect simplified architecture
49
45
  description="Backend API and SSE server for the A2A Web UI, hosted by Solace AI Connector.",
50
46
  )
51
47
 
52
48
 
53
- def setup_dependencies(component: "WebUIBackendComponent"):
49
+ def setup_dependencies(component: "WebUIBackendComponent", persistence_service=None):
54
50
  """
55
- Sets up the component instance reference and configures middleware and routers
56
- that depend on the component being available.
57
- Called from the component's startup sequence.
51
+ This function initializes the modern architecture while maintaining full
52
+ backward compatibility with existing API contracts.
53
+
54
+ If persistence_service is None, runs in compatibility mode with in-memory sessions.
58
55
  """
59
- log.info("Setting up FastAPI dependencies, middleware, and routers...")
56
+
57
+ if persistence_service:
58
+ database_url = persistence_service.engine.url.__str__()
59
+ global database_service
60
+ database_service = DatabaseService(database_url)
61
+ log.info("Database service initialized")
62
+
63
+ from .infrastructure.dependency_injection.container import initialize_container
64
+
65
+ initialize_container(database_url)
66
+ log.info("Persistence enabled - sessions will be stored in database")
67
+ else:
68
+ from .infrastructure.dependency_injection.container import initialize_container
69
+
70
+ initialize_container()
71
+ log.warning(
72
+ "No persistence service provided - using in-memory session storage (data not persisted across restarts)"
73
+ )
74
+ log.info("This maintains backward compatibility for existing SAM installations")
75
+
60
76
  dependencies.set_component_instance(component)
61
77
 
78
+ if persistence_service:
79
+ log.info("Checking database migrations...")
80
+ try:
81
+ inspector = sa.inspect(persistence_service.engine)
82
+ existing_tables = inspector.get_table_names()
83
+
84
+ if not existing_tables or "sessions" not in existing_tables:
85
+ log.info("Running database migrations...")
86
+ alembic_cfg = Config()
87
+ alembic_cfg.set_main_option(
88
+ "script_location",
89
+ os.path.join(os.path.dirname(__file__), "alembic"),
90
+ )
91
+ alembic_cfg.set_main_option("sqlalchemy.url", database_url)
92
+ command.upgrade(alembic_cfg, "head")
93
+ log.info("Database migrations complete.")
94
+ else:
95
+ log.info("Database tables already exist, skipping migrations.")
96
+ except Exception as e:
97
+ log.warning(
98
+ f"Migration check failed, attempting to run migrations anyway: {e}"
99
+ )
100
+ try:
101
+ alembic_cfg = Config()
102
+ alembic_cfg.set_main_option(
103
+ "script_location",
104
+ os.path.join(os.path.dirname(__file__), "alembic"),
105
+ )
106
+ alembic_cfg.set_main_option("sqlalchemy.url", database_url)
107
+ command.upgrade(alembic_cfg, "head")
108
+ log.info("Database migrations complete.")
109
+ except Exception as migration_error:
110
+ log.warning(f"Migration failed but continuing: {migration_error}")
111
+
112
+ dependencies.set_persistence_service(persistence_service)
113
+ else:
114
+ log.info("Skipping database migrations - no persistence service configured")
115
+
62
116
  webui_app = component.get_app()
63
117
  app_config = {}
64
118
  if webui_app:
@@ -83,6 +137,7 @@ def setup_dependencies(component: "WebUIBackendComponent"):
83
137
  "frontend_redirect_url": app_config.get(
84
138
  "frontend_redirect_url", "http://localhost:3000"
85
139
  ),
140
+ "persistence_enabled": persistence_service is not None,
86
141
  }
87
142
 
88
143
  dependencies.set_api_config(api_config_dict)
@@ -214,17 +269,80 @@ def setup_dependencies(component: "WebUIBackendComponent"):
214
269
  return
215
270
 
216
271
  user_info = userinfo_response.json()
217
- email_from_auth = user_info.get("email")
272
+ log.info(
273
+ "AuthMiddleware: Raw user info from OAuth provider: %s",
274
+ user_info,
275
+ )
276
+
277
+ # Priority order for user identifier (most specific to least specific)
278
+ user_identifier = (
279
+ user_info.get("sub") # Standard OIDC subject claim
280
+ or user_info.get("client_id") # Mini IDP and some custom IDPs
281
+ or user_info.get("username") # Mini IDP returns username field
282
+ or user_info.get("oid") # Azure AD object ID
283
+ or user_info.get(
284
+ "preferred_username"
285
+ ) # Common in enterprise IDPs
286
+ or user_info.get("upn") # Azure AD User Principal Name
287
+ or user_info.get("unique_name") # Some Azure configurations
288
+ or user_info.get("email") # Fallback to email
289
+ or user_info.get("name") # Last resort
290
+ or user_info.get("azp") # Authorized party (rare but possible)
291
+ )
218
292
 
219
- if not email_from_auth:
293
+ # IMPORTANT: If the extracted identifier is "Unknown", it means the IDP
294
+ # didn't properly authenticate or is misconfigured. Use a fallback.
295
+ if user_identifier and user_identifier.lower() == "unknown":
296
+ log.warning(
297
+ "AuthMiddleware: IDP returned 'Unknown' as user identifier. This indicates misconfiguration. Using fallback."
298
+ )
299
+ # In development mode with mini IDP, default to sam_dev_user
300
+ # This is a workaround for the OAuth2 proxy service returning "Unknown"
301
+ user_identifier = "sam_dev_user" # Fallback for development
302
+ log.info(
303
+ "AuthMiddleware: Using development fallback user: sam_dev_user"
304
+ )
305
+
306
+ # Extract email separately (may be different from user identifier)
307
+ email_from_auth = (
308
+ user_info.get("email")
309
+ or user_info.get("preferred_username")
310
+ or user_info.get("upn")
311
+ or user_identifier
312
+ )
313
+
314
+ # Extract display name
315
+ display_name = (
316
+ user_info.get("name")
317
+ or user_info.get("given_name", "")
318
+ + " "
319
+ + user_info.get("family_name", "")
320
+ or user_info.get("preferred_username")
321
+ or user_identifier
322
+ ).strip()
323
+
324
+ log.info(
325
+ "AuthMiddleware: Extracted user identifier: %s, email: %s, name: %s",
326
+ user_identifier,
327
+ email_from_auth,
328
+ display_name,
329
+ )
330
+
331
+ if not user_identifier or user_identifier.lower() in [
332
+ "null",
333
+ "none",
334
+ "",
335
+ ]:
220
336
  log.error(
221
- "AuthMiddleware: Email not found in user info from external auth provider."
337
+ "AuthMiddleware: No valid user identifier from OAuth provider. Full user info: %s. Expected valid user identifier.",
338
+ user_info,
222
339
  )
223
340
  response = JSONResponse(
224
341
  status_code=status.HTTP_401_UNAUTHORIZED,
225
342
  content={
226
- "detail": "User email not provided by auth provider",
227
- "error_type": "email_missing",
343
+ "detail": "OAuth provider returned no valid user identifier. Provider must return at least one of: sub, username, client_id, preferred_username, email, or name field.",
344
+ "error_type": "invalid_user_identifier_from_provider",
345
+ "received_user_info": user_info,
228
346
  },
229
347
  )
230
348
  await response(scope, receive, send)
@@ -232,24 +350,51 @@ def setup_dependencies(component: "WebUIBackendComponent"):
232
350
 
233
351
  identity_service = self.component.identity_service
234
352
  if not identity_service:
353
+ # Make absolutely sure we have a valid user ID - never "Unknown"
354
+ final_user_id = (
355
+ user_identifier or email_from_auth or "sam_dev_user"
356
+ )
357
+ if not final_user_id or final_user_id.lower() in [
358
+ "unknown",
359
+ "null",
360
+ "none",
361
+ "",
362
+ ]:
363
+ final_user_id = "sam_dev_user"
364
+ log.warning(
365
+ "AuthMiddleware: Had to use fallback user ID due to invalid identifier: %s",
366
+ user_identifier,
367
+ )
368
+
235
369
  log.error(
236
- "AuthMiddleware: Internal IdentityService not configured on component. Falling back to using email as ID."
370
+ "AuthMiddleware: Internal IdentityService not configured on component. Using user ID: %s",
371
+ final_user_id,
237
372
  )
238
373
  request.state.user = {
239
- "id": email_from_auth,
240
- "email": email_from_auth,
241
- "name": user_info.get("name", email_from_auth),
374
+ "id": final_user_id,
375
+ "email": email_from_auth or final_user_id,
376
+ "name": display_name or final_user_id,
242
377
  "authenticated": True,
243
378
  "auth_method": "oidc",
244
379
  }
380
+ log.info(
381
+ "AuthMiddleware: Set fallback user state with id: %s",
382
+ final_user_id,
383
+ )
245
384
  else:
385
+ # Try to look up user profile using the email or user identifier
386
+ lookup_value = (
387
+ email_from_auth
388
+ if "@" in email_from_auth
389
+ else user_identifier
390
+ )
246
391
  user_profile = await identity_service.get_user_profile(
247
- {identity_service.lookup_key: email_from_auth}
392
+ {identity_service.lookup_key: lookup_value}
248
393
  )
249
394
  if not user_profile:
250
395
  log.error(
251
396
  "AuthMiddleware: User '%s' authenticated but not found in internal IdentityService.",
252
- email_from_auth,
397
+ lookup_value,
253
398
  )
254
399
  response = JSONResponse(
255
400
  status_code=status.HTTP_403_FORBIDDEN,
@@ -262,10 +407,18 @@ def setup_dependencies(component: "WebUIBackendComponent"):
262
407
  return
263
408
 
264
409
  request.state.user = user_profile.copy()
410
+ # Ensure the ID is set from the OAuth provider if not present in the profile
411
+ if not request.state.user.get("id"):
412
+ request.state.user["id"] = user_identifier
413
+ # Also ensure email and name are set if not in profile
414
+ if not request.state.user.get("email"):
415
+ request.state.user["email"] = email_from_auth
416
+ if not request.state.user.get("name"):
417
+ request.state.user["name"] = display_name
265
418
  request.state.user["authenticated"] = True
266
419
  request.state.user["auth_method"] = "oidc"
267
- log.debug(
268
- "AuthMiddleware: Enriched and stored user profile for id: %s",
420
+ log.info(
421
+ "AuthMiddleware: Set enriched user profile with id: %s",
269
422
  request.state.user.get("id"),
270
423
  )
271
424
 
@@ -289,9 +442,22 @@ def setup_dependencies(component: "WebUIBackendComponent"):
289
442
  )
290
443
  await response(scope, receive, send)
291
444
  return
445
+ else:
446
+ # If auth is not used, set a default user
447
+ request.state.user = {
448
+ "id": "sam_dev_user",
449
+ "name": "Sam Dev User",
450
+ "email": "sam@dev.local",
451
+ "authenticated": True,
452
+ "auth_method": "development",
453
+ }
454
+ log.debug(
455
+ "AuthMiddleware: Set development user state with id: sam_dev_user"
456
+ )
292
457
 
293
458
  await self.app(scope, receive, send)
294
459
 
460
+ # Add middleware
295
461
  allowed_origins = component.get_cors_origins()
296
462
  app.add_middleware(
297
463
  CORSMiddleware,
@@ -309,10 +475,30 @@ def setup_dependencies(component: "WebUIBackendComponent"):
309
475
  app.add_middleware(AuthMiddleware, component=component)
310
476
  log.info("AuthMiddleware added.")
311
477
 
478
+ # Mount API routers
312
479
  api_prefix = "/api/v1"
480
+
481
+ # Mount persistence-aware controllers (your original controllers with full functionality)
482
+ # These provide the complete API surface with database persistence
483
+ app.include_router(
484
+ session_router, prefix=api_prefix, tags=["Sessions"]
485
+ ) # Provides /api/v1/sessions/* endpoints
486
+ app.include_router(
487
+ user_router, prefix=f"{api_prefix}/users", tags=["Users"]
488
+ ) # Provides /api/v1/users/me
489
+ app.include_router(
490
+ task_router, prefix=f"{api_prefix}/tasks", tags=["Tasks"]
491
+ ) # Provides /api/v1/tasks/send, /subscribe, /cancel
492
+
493
+ # Mount new A2A SDK routers with different paths to avoid conflicts
313
494
  app.include_router(config.router, prefix=api_prefix, tags=["Config"])
314
- app.include_router(agents.router, prefix=api_prefix, tags=["Agents"])
315
- app.include_router(tasks.router, prefix=api_prefix, tags=["Tasks"])
495
+ app.include_router(agent_cards.router, prefix=api_prefix, tags=["Agent Cards"])
496
+ # New A2A message endpoints (non-conflicting paths)
497
+ app.include_router(
498
+ tasks.router, prefix=api_prefix, tags=["A2A Messages"]
499
+ ) # Provides /api/v1/message:send, /message:stream
500
+ # Note: We only use the full-featured session_router (from api/controllers/session_controller.py)
501
+ # which provides complete session management with database persistence
316
502
  app.include_router(sse.router, prefix=f"{api_prefix}/sse", tags=["SSE"])
317
503
  app.include_router(
318
504
  artifacts.router, prefix=f"{api_prefix}/artifacts", tags=["Artifacts"]
@@ -322,18 +508,15 @@ def setup_dependencies(component: "WebUIBackendComponent"):
322
508
  prefix=f"{api_prefix}/visualization",
323
509
  tags=["Visualization"],
324
510
  )
325
- app.include_router(
326
- sessions.router, prefix=f"{api_prefix}/sessions", tags=["Sessions"]
327
- )
328
511
  app.include_router(
329
512
  people.router,
330
513
  prefix=api_prefix,
331
514
  tags=["People"],
332
515
  )
333
516
  app.include_router(auth.router, prefix=api_prefix, tags=["Auth"])
334
- app.include_router(users.router, prefix=f"{api_prefix}/users", tags=["Users"])
335
- log.info("API routers mounted under prefix: %s", api_prefix)
517
+ log.info("Legacy routers mounted for endpoints not yet migrated")
336
518
 
519
+ # Mount static files
337
520
  current_dir = os.path.dirname(os.path.abspath(__file__))
338
521
  root_dir = Path(os.path.normpath(os.path.join(current_dir, "..", "..")))
339
522
  static_files_dir = Path.joinpath(root_dir, "client", "webui", "frontend", "static")
@@ -358,7 +541,10 @@ def setup_dependencies(component: "WebUIBackendComponent"):
358
541
 
359
542
  @app.exception_handler(HTTPException)
360
543
  async def http_exception_handler(request: FastAPIRequest, exc: HTTPException):
361
- """Handles FastAPI's built-in HTTPExceptions and formats them as JSONRPC errors."""
544
+ """
545
+ HTTP exception handler with automatic format detection.
546
+ Returns JSON-RPC format for tasks/SSE endpoints, REST format for others.
547
+ """
362
548
  log.warning(
363
549
  "HTTP Exception: Status=%s, Detail=%s, Request: %s %s",
364
550
  exc.status_code,
@@ -366,37 +552,58 @@ async def http_exception_handler(request: FastAPIRequest, exc: HTTPException):
366
552
  request.method,
367
553
  request.url,
368
554
  )
369
- error_data = None
370
- error_code = InternalError().code
371
- error_message = str(exc.detail)
372
-
373
- if isinstance(exc.detail, dict):
374
- if "code" in exc.detail and "message" in exc.detail:
375
- error_code = exc.detail["code"]
376
- error_message = exc.detail["message"]
377
- error_data = exc.detail.get("data")
555
+
556
+ # Check if this is a JSON-RPC endpoint (tasks and SSE endpoints use JSON-RPC)
557
+ is_jsonrpc_endpoint = request.url.path.startswith(
558
+ "/api/v1/tasks"
559
+ ) or request.url.path.startswith("/api/v1/sse")
560
+
561
+ if is_jsonrpc_endpoint:
562
+ # Use JSON-RPC format for tasks and SSE endpoints
563
+ error_data = None
564
+ error_code = InternalError().code
565
+ error_message = str(exc.detail)
566
+
567
+ if isinstance(exc.detail, dict):
568
+ if "code" in exc.detail and "message" in exc.detail:
569
+ error_code = exc.detail["code"]
570
+ error_message = exc.detail["message"]
571
+ error_data = exc.detail.get("data")
572
+ else:
573
+ error_data = exc.detail
574
+ elif isinstance(exc.detail, str):
575
+ if exc.status_code == status.HTTP_400_BAD_REQUEST:
576
+ error_code = -32600
577
+ elif exc.status_code == status.HTTP_404_NOT_FOUND:
578
+ error_code = -32601
579
+ error_message = "Resource not found"
580
+
581
+ error_obj = JSONRPCError(
582
+ code=error_code, message=error_message, data=error_data
583
+ )
584
+ response = A2AJSONRPCResponse(error=error_obj)
585
+ return JSONResponse(
586
+ status_code=exc.status_code, content=response.model_dump(exclude_none=True)
587
+ )
588
+ else:
589
+ # Use standard REST format for sessions and other REST endpoints
590
+ if isinstance(exc.detail, dict):
591
+ error_response = exc.detail
592
+ elif isinstance(exc.detail, str):
593
+ error_response = {"detail": exc.detail}
378
594
  else:
379
- error_data = exc.detail
595
+ error_response = {"detail": str(exc.detail)}
380
596
 
381
- elif isinstance(exc.detail, str):
382
- if exc.status_code == status.HTTP_400_BAD_REQUEST:
383
- error_code = -32600
384
- elif exc.status_code == status.HTTP_404_NOT_FOUND:
385
- error_code = -32601
386
- error_message = "Resource not found"
387
-
388
- error_obj = JSONRPCError(code=error_code, message=error_message, data=error_data)
389
- response = a2a.create_error_response(error=error_obj, request_id=None)
390
- return JSONResponse(
391
- status_code=exc.status_code, content=response.model_dump(exclude_none=True)
392
- )
597
+ return JSONResponse(status_code=exc.status_code, content=error_response)
393
598
 
394
599
 
395
600
  @app.exception_handler(RequestValidationError)
396
601
  async def validation_exception_handler(
397
602
  request: FastAPIRequest, exc: RequestValidationError
398
603
  ):
399
- """Handles Pydantic validation errors (422) and formats them."""
604
+ """
605
+ Handles Pydantic validation errors with format detection.
606
+ """
400
607
  log.warning(
401
608
  "Request Validation Error: %s, Request: %s %s",
402
609
  exc.errors(),
@@ -414,7 +621,9 @@ async def validation_exception_handler(
414
621
 
415
622
  @app.exception_handler(Exception)
416
623
  async def generic_exception_handler(request: FastAPIRequest, exc: Exception):
417
- """Handles any other unexpected exceptions."""
624
+ """
625
+ Handles any other unexpected exceptions with format detection.
626
+ """
418
627
  log.exception(
419
628
  "Unhandled Exception: %s, Request: %s %s", exc, request.method, request.url
420
629
  )
@@ -433,8 +642,3 @@ async def read_root():
433
642
  """Basic health check endpoint."""
434
643
  log.debug("Health check endpoint '/health' called")
435
644
  return {"status": "A2A Web UI Backend is running"}
436
-
437
-
438
- log.info(
439
- "FastAPI application instance created (endpoints/middleware/static files setup deferred until component startup)."
440
- )
@@ -9,19 +9,19 @@ from solace_ai_connector.common.log import log
9
9
 
10
10
  from ....common.agent_registry import AgentRegistry
11
11
  from a2a.types import AgentCard
12
- from ....gateway.http_sse.dependencies import get_agent_registry
12
+ from ..dependencies import get_agent_registry
13
13
 
14
14
  router = APIRouter()
15
15
 
16
16
 
17
- @router.get("/agents", response_model=List[AgentCard])
18
- async def get_discovered_agents(
17
+ @router.get("/agentCards", response_model=List[AgentCard])
18
+ async def get_discovered_agent_cards(
19
19
  agent_registry: AgentRegistry = Depends(get_agent_registry),
20
20
  ):
21
21
  """
22
- Retrieves a list of all currently discovered A2A agents.
22
+ Retrieves a list of all currently discovered A2A agents' cards.
23
23
  """
24
- log_prefix = "[GET /api/v1/agents] "
24
+ log_prefix = "[GET /api/v1/agentCards] "
25
25
  log.info("%sRequest received.", log_prefix)
26
26
  try:
27
27
  agent_names = agent_registry.get_agent_names()
@@ -31,10 +31,10 @@ async def get_discovered_agents(
31
31
  if agent_registry.get_agent(name)
32
32
  ]
33
33
 
34
- log.info("%sReturning %d discovered agents.", log_prefix, len(agents))
34
+ log.info("%sReturning %d discovered agent cards.", log_prefix, len(agents))
35
35
  return agents
36
36
  except Exception as e:
37
- log.exception("%sError retrieving discovered agents: %s", log_prefix, e)
37
+ log.exception("%sError retrieving discovered agent cards: %s", log_prefix, e)
38
38
  raise HTTPException(
39
39
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
40
40
  detail="Internal server error retrieving agent list.",