Skip to content

Commit cc0ca2b

Browse files
committed
feat(DATAGO-118497, DATAGO-118496): Platform Service independence and shared utilities
This PR completes the backend architecture split for Platform Service, making it fully independent from WebUI Gateway. ## Platform Service Enhancements (DATAGO-118497) ### Background Task Migration - Added broker connection to Platform Service component - Initialized agent registry for deployment monitoring - Implemented heartbeat listener for deployer status tracking - Implemented deployment status checker (runs every 60s) - Added graceful cleanup for all background tasks ### Message Publishing - Added publish_a2a() method to Platform Service - Can now send deployment commands to deployer - Topics: {namespace}/deployer/agent/{id}/{deploy|update|undeploy} ### Message Receiving - Receives deployer heartbeats: {namespace}/deployer/heartbeat - Receives agent cards: {namespace}/a2a/agent-cards (via AgentRegistry) ### Configuration - Added deployment_timeout_minutes (default: 5) - Added heartbeat_timeout_seconds (default: 90) - Added deployment_check_interval_seconds (default: 60) - Created examples/services/platform_service_example.yaml ## OAuth Implementation (DATAGO-118496) - Extracted OAuth middleware to shared/auth/middleware.py - Platform Service now validates bearer tokens with OAuth service - Supports both production (use_authorization=true) and dev mode - Same auth implementation for gateways and services ## Shared Utilities Layer (Architecture Improvement) ### Created shared/ Module - Moved 13 files from http_sse/shared/ to shared/ - Created 5 subdirectories: api, database, exceptions, auth, utils - Extracted auth dependencies from http_sse to shared/auth - Total: ~2,000 lines of reusable code ### Benefits - Gateways and services import from shared/ (no cross-dependencies) - Platform Service no longer depends on http_sse - Clean architectural boundary - Enables future services (billing, monitoring, etc.) ## WebUI Gateway Cleanup (Chat-Only) ### Removed - Enterprise router mounting (no more /api/v1/enterprise/*) - Background task startup/shutdown - Platform migration calls - Enterprise background task dependencies ### Result - WebUI Gateway now serves chat endpoints only - Single database (WEB_UI_GATEWAY_DATABASE_URL) - No platform functionality ## Enterprise Import Updates - Updated imports: http_sse.shared.* → shared.* - Updated imports: webui_backend → platform_service - Uses shared auth dependencies - 31 files updated (54 import replacements) ## Breaking Changes - http_sse/shared/ module removed (raises ImportError) - Enterprise routers no longer mounted in WebUI Gateway - Platform database no longer used by WebUI Gateway - Users must run Platform Service separately for agent management ## Migration Path ### Before (Merged) - Single service on port 8000 (chat + platform) ### After (Split) - WebUI Gateway on port 8000 (chat only) - Platform Service on port 8001 (platform management) ## Testing - Verified Platform Service starts with broker connection - Verified background tasks initialize successfully - Verified OAuth middleware loads - All imports migrated successfully
1 parent e338b39 commit cc0ca2b

35 files changed

+1117
-691
lines changed

examples/gateways/webui_gateway_example.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ apps:
3636
type: "sql"
3737
database_url: "${WEB_UI_GATEWAY_DATABASE_URL, sqlite:///webui-gateway.db}"
3838
default_behavior: "PERSISTENT"
39+
40+
# --- Platform Service Configuration ---
41+
# URL for Platform Service (runs separately on port 8001)
42+
# Frontend will route /api/v1/enterprise/* requests to this URL
43+
platform_service:
44+
url: "${PLATFORM_SERVICE_URL, http://localhost:8001}"
3945
# --- Optional with Defaults ---
4046
model: *general_model # Optional: defaults to None - Points to shared_config.yaml
4147
gateway_id: ${WEBUI_GATEWAY_ID} # Optional: Unique ID for this instance. If omitted, one will be generated.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Platform Service Configuration for Solace Agent Mesh
2+
# Serves platform management endpoints: agents, connectors, deployments, toolsets
3+
4+
log:
5+
stdout_log_level: INFO
6+
log_file_level: DEBUG
7+
log_file: platform_service.log
8+
9+
# Shared SAM config (broker connection, models, services)
10+
!include ../shared_config.yaml
11+
12+
apps:
13+
- name: platform_service_app
14+
app_base_path: .
15+
app_module: solace_agent_mesh.services.platform.app
16+
17+
# Broker connection (required for heartbeat monitoring and agent registry)
18+
broker:
19+
<<: *broker_connection # Points to shared_config.yaml
20+
21+
# --- App Level Config (Validated by PlatformServiceApp.app_schema) ---
22+
app_config:
23+
# --- Required ---
24+
namespace: ${NAMESPACE} # Namespace for A2A communication
25+
database_url: "${PLATFORM_DATABASE_URL}" # Platform database (agents, connectors, deployments)
26+
27+
# --- FastAPI Server Configuration ---
28+
fastapi_host: ${PLATFORM_API_HOST, localhost}
29+
fastapi_port: ${PLATFORM_API_PORT, 8001}
30+
cors_allowed_origins:
31+
- "http://localhost:3000"
32+
- "http://127.0.0.1:3000"
33+
# Add other origins as needed
34+
35+
# --- OAuth2 Authentication ---
36+
external_auth_service_url: ${EXTERNAL_AUTH_SERVICE_URL}
37+
external_auth_provider: ${EXTERNAL_AUTH_PROVIDER, generic}
38+
use_authorization: ${USE_AUTHORIZATION, false} # Set to true for production
39+
40+
# --- Background Task Configuration ---
41+
# Deployment monitoring
42+
deployment_timeout_minutes: ${DEPLOYMENT_TIMEOUT_MINUTES, 5}
43+
deployment_check_interval_seconds: ${DEPLOYMENT_CHECK_INTERVAL, 60}
44+
45+
# Deployer heartbeat monitoring
46+
heartbeat_timeout_seconds: ${HEARTBEAT_TIMEOUT_SECONDS, 90}

src/solace_agent_mesh/gateway/http_sse/app.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,19 @@ class WebUIBackendApp(BaseGatewayApp):
193193
"required": False,
194194
"type": "dict",
195195
"default": {},
196-
"description": "Configuration for the Platform Service (enterprise features: agents, connectors, deployments).",
196+
"description": "Configuration for connecting to the Platform Service (runs separately on port 8001).",
197197
"dict_schema": {
198-
"database_url": {
198+
"url": {
199199
"type": "string",
200200
"required": False,
201-
"default": None,
201+
"default": "",
202202
"description": (
203-
"Database URL for platform data (agents, connectors, deployments). "
204-
"REQUIRED for platform features to be available. "
205-
"Example: postgresql://user:pass@host:5432/platform_db"
203+
"Platform Service URL for frontend API routing to enterprise endpoints. "
204+
"Frontend will call this URL for /api/v1/enterprise/* requests. "
205+
"Examples: "
206+
" - Docker: http://platform-service:8001 "
207+
" - K8s: http://platform-service:8001 "
208+
" - Local: http://localhost:8001"
206209
),
207210
},
208211
},

src/solace_agent_mesh/gateway/http_sse/component.py

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def __init__(self, **kwargs):
212212
)
213213

214214
platform_config = self.get_config("platform_service", {})
215-
self.platform_database_url = platform_config.get("database_url")
215+
self.platform_service_url = platform_config.get("url", "")
216216
component_config = self.get_config("component_config", {})
217217
app_config = component_config.get("app_config", {})
218218

@@ -1229,7 +1229,7 @@ def _start_fastapi_server(self):
12291229

12301230
self.fastapi_app = fastapi_app_instance
12311231

1232-
setup_dependencies(self, self.database_url, self.platform_database_url)
1232+
setup_dependencies(self, self.database_url)
12331233

12341234
# Instantiate services that depend on the database session factory.
12351235
# This must be done *after* setup_dependencies has run.
@@ -1346,72 +1346,13 @@ async def capture_event_loop():
13461346
)
13471347
self.stop_signal.set()
13481348

1349-
try:
1350-
from solace_agent_mesh_enterprise.init_enterprise import (
1351-
start_enterprise_background_tasks,
1352-
)
1353-
1354-
log.info(
1355-
"%s Starting enterprise background tasks...",
1356-
self.log_identifier,
1357-
)
1358-
await start_enterprise_background_tasks(self)
1359-
log.info(
1360-
"%s Enterprise background tasks started successfully",
1361-
self.log_identifier,
1362-
)
1363-
except ImportError:
1364-
log.debug(
1365-
"%s Enterprise package not available - skipping background tasks",
1366-
self.log_identifier,
1367-
)
1368-
except RuntimeError as enterprise_err:
1369-
log.warning(
1370-
"%s Enterprise background tasks disabled: %s - Community features will continue normally",
1371-
self.log_identifier,
1372-
enterprise_err,
1373-
)
1374-
except Exception as enterprise_err:
1375-
log.error(
1376-
"%s Failed to start enterprise background tasks: %s - Community features will continue normally",
1377-
self.log_identifier,
1378-
enterprise_err,
1379-
exc_info=True,
1380-
)
1381-
13821349
@self.fastapi_app.on_event("shutdown")
13831350
async def shutdown_event():
13841351
log.info(
13851352
"%s [_start_listener] FastAPI shutdown event triggered.",
13861353
self.log_identifier,
13871354
)
13881355

1389-
try:
1390-
from solace_agent_mesh_enterprise.init_enterprise import (
1391-
stop_enterprise_background_tasks,
1392-
)
1393-
1394-
log.info(
1395-
"%s Stopping enterprise background tasks...",
1396-
self.log_identifier,
1397-
)
1398-
await stop_enterprise_background_tasks()
1399-
log.info(
1400-
"%s Enterprise background tasks stopped", self.log_identifier
1401-
)
1402-
except ImportError:
1403-
log.debug(
1404-
"%s Enterprise package not available - no background tasks to stop",
1405-
self.log_identifier,
1406-
)
1407-
except Exception as enterprise_err:
1408-
log.error(
1409-
"%s Failed to stop enterprise background tasks: %s",
1410-
self.log_identifier,
1411-
enterprise_err,
1412-
exc_info=True,
1413-
)
1414-
14151356
self.fastapi_thread = threading.Thread(
14161357
target=self.uvicorn_server.run, daemon=True, name="FastAPI_Thread"
14171358
)

src/solace_agent_mesh/gateway/http_sse/dependencies.py

Lines changed: 6 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -409,56 +409,13 @@ async def get_user_config(
409409
)
410410

411411

412-
class ValidatedUserConfig:
413-
"""
414-
FastAPI dependency class for validating user scopes and returning user config.
415-
416-
This class creates a callable dependency that validates a user has the required
417-
scopes before allowing access to protected endpoints.
418-
419-
Args:
420-
required_scopes: List of scope strings required for authorization
421-
422-
Raises:
423-
HTTPException: 403 if user lacks required scopes
424-
425-
Example:
426-
@router.get("/artifacts")
427-
async def list_artifacts(
428-
user_config: dict = Depends(ValidatedUserConfig(["tool:artifact:list"])),
429-
):
430-
"""
431-
432-
def __init__(self, required_scopes: list[str]):
433-
self.required_scopes = required_scopes
434-
435-
async def __call__(
436-
self,
437-
request: Request,
438-
config_resolver: ConfigResolver = Depends(get_config_resolver),
439-
user_config: dict[str, Any] = Depends(get_user_config),
440-
) -> dict[str, Any]:
441-
user_id = user_config.get("user_profile", {}).get("id")
442-
443-
log.debug(
444-
f"ValidatedUserConfig called for user_id: {user_id} with required scopes: {self.required_scopes}"
445-
)
446-
447-
# Validate scopes
448-
if not config_resolver.is_feature_enabled(
449-
user_config,
450-
{"tool_metadata": {"required_scopes": self.required_scopes}},
451-
{},
452-
):
453-
log.warning(
454-
f"Authorization denied for user '{user_id}'. Required scopes: {self.required_scopes}"
455-
)
456-
raise HTTPException(
457-
status_code=status.HTTP_403_FORBIDDEN,
458-
detail=f"Not authorized. Required scopes: {self.required_scopes}",
459-
)
412+
# DEPRECATED: Import from shared location
413+
# Re-export for backward compatibility
414+
from solace_agent_mesh.shared.auth.dependencies import ValidatedUserConfig
460415

461-
return user_config
416+
# Note: ValidatedUserConfig implementation moved to:
417+
# src/solace_agent_mesh/shared/auth/dependencies.py
418+
# This re-export will be removed in v2.0.0
462419

463420

464421
def get_shared_artifact_service(

src/solace_agent_mesh/gateway/http_sse/main.py

Lines changed: 11 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -448,58 +448,28 @@ def _run_community_migrations(database_url: str) -> None:
448448
) from migration_error
449449

450450

451-
def _run_enterprise_migrations(
452-
component: "WebUIBackendComponent", database_url: str
453-
) -> None:
454-
"""
455-
Run migrations for enterprise features like advanced analytics, audit logs, etc.
456-
This is optional and only runs if the enterprise package is available.
457-
"""
458-
try:
459-
from solace_agent_mesh_enterprise.webui_backend.migration_runner import (
460-
run_migrations,
461-
)
462-
463-
webui_app = component.get_app()
464-
app_config = getattr(webui_app, "app_config", {}) if webui_app else {}
465-
log.info("Starting enterprise migrations...")
466-
run_migrations(database_url, app_config)
467-
log.info("Enterprise migrations completed")
468-
except (ImportError, ModuleNotFoundError):
469-
log.debug("Enterprise module not found - skipping enterprise migrations")
470-
except Exception as e:
471-
log.error("Enterprise migration failed: %s", e)
472-
log.error("Advanced features may be unavailable")
473-
raise RuntimeError(f"Enterprise database migration failed: {e}") from e
474451

475452

476453
def _setup_database(
477454
component: "WebUIBackendComponent",
478455
database_url: str,
479-
platform_database_url: str = None
480456
) -> None:
481457
"""
482-
Initialize database connections and run all required migrations.
483-
Sets up both runtime and platform database schemas.
458+
Initialize database and run migrations for WebUI Gateway (chat only).
459+
460+
Platform database is no longer used by WebUI Gateway.
461+
Platform migrations are handled by Platform Service.
484462
485463
Args:
486464
component: WebUIBackendComponent instance
487-
database_url: Runtime database URL (sessions, tasks, chat) - REQUIRED
488-
platform_database_url: Platform database URL (agents, connectors, deployments).
489-
If None, platform features will be unavailable.
465+
database_url: Chat database URL (sessions, tasks, feedback) - REQUIRED
490466
"""
491467
dependencies.init_database(database_url)
492468
log.info("Persistence enabled - sessions will be stored in database")
493469
log.info("Running database migrations...")
494470

495471
_run_community_migrations(database_url)
496472

497-
if platform_database_url:
498-
log.info("Platform database configured - running migrations")
499-
_run_enterprise_migrations(component, platform_database_url)
500-
else:
501-
log.info("No platform database configured - skipping platform migrations")
502-
503473

504474
def _get_app_config(component: "WebUIBackendComponent") -> dict:
505475
webui_app = component.get_app()
@@ -536,17 +506,14 @@ def _create_api_config(app_config: dict, database_url: str) -> dict:
536506
def setup_dependencies(
537507
component: "WebUIBackendComponent",
538508
database_url: str = None,
539-
platform_database_url: str = None
540509
):
541510
"""
542-
Initialize dependencies for both runtime and platform databases.
511+
Initialize dependencies for WebUI Gateway (chat only).
543512
544513
Args:
545514
component: WebUIBackendComponent instance
546-
database_url: Runtime database URL (sessions, tasks, chat).
515+
database_url: Chat database URL (sessions, tasks, feedback).
547516
If None, runs in compatibility mode with in-memory sessions.
548-
platform_database_url: Platform database URL (agents, connectors, deployments).
549-
If None, platform features will be unavailable (returns 501).
550517
551518
This function is idempotent and safe to call multiple times.
552519
"""
@@ -559,7 +526,7 @@ def setup_dependencies(
559526
dependencies.set_component_instance(component)
560527

561528
if database_url:
562-
_setup_database(component, database_url, platform_database_url)
529+
_setup_database(component, database_url)
563530
else:
564531
log.warning(
565532
"No database URL provided - using in-memory session storage (data not persisted across restarts)"
@@ -626,35 +593,11 @@ def _setup_routers() -> None:
626593
app.include_router(speech.router, prefix=f"{api_prefix}/speech", tags=["Speech"])
627594
log.info("Legacy routers mounted for endpoints not yet migrated")
628595

629-
# Register shared exception handlers from community repo
630-
from .shared.exception_handlers import register_exception_handlers
596+
# Register shared exception handlers
597+
from solace_agent_mesh.shared.exceptions.exception_handlers import register_exception_handlers
631598

632599
register_exception_handlers(app)
633-
log.info("Registered shared exception handlers from community repo")
634-
635-
# Mount enterprise routers if available
636-
try:
637-
from solace_agent_mesh_enterprise.webui_backend.routers import (
638-
get_enterprise_routers,
639-
)
640-
641-
enterprise_routers = get_enterprise_routers()
642-
for router_config in enterprise_routers:
643-
app.include_router(
644-
router_config["router"],
645-
prefix=router_config["prefix"],
646-
tags=router_config["tags"],
647-
)
648-
log.info("Mounted %d enterprise routers", len(enterprise_routers))
649-
650-
except ImportError:
651-
log.debug("No enterprise package detected - skipping enterprise routers")
652-
except ModuleNotFoundError:
653-
log.debug(
654-
"Enterprise module not found - skipping enterprise routers and exception handlers"
655-
)
656-
except Exception as e:
657-
log.warning("Failed to load enterprise routers and exception handlers: %s", e)
600+
log.info("Registered shared exception handlers")
658601

659602

660603
def _setup_static_files() -> None:

src/solace_agent_mesh/gateway/http_sse/routers/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,12 @@ async def get_app_config(
267267
"ttsProvider": tts_provider,
268268
}
269269

270+
platform_config = component.get_config("platform_service", {})
271+
platform_service_url = platform_config.get("url", "")
272+
270273
config_data = {
271274
"frontend_server_url": "",
275+
"frontend_enterprise_server_url": platform_service_url,
272276
"frontend_auth_login_url": component.get_config(
273277
"frontend_auth_login_url", ""
274278
),

0 commit comments

Comments
 (0)