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.
- solace_agent_mesh/agent/adk/runner.py +18 -12
- solace_agent_mesh/agent/adk/services.py +3 -3
- solace_agent_mesh/agent/protocol/event_handlers.py +27 -21
- solace_agent_mesh/agent/sac/app.py +1 -1
- solace_agent_mesh/agent/sac/component.py +0 -1
- solace_agent_mesh/assets/docs/404.html +2 -2
- solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js → main.08d30374.js} +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +2 -2
- 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
- solace_agent_mesh/assets/docs/docs/documentation/migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +2 -2
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +2 -2
- solace_agent_mesh/assets/docs/lunr-index-1757433031159.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1757433031159.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +125 -48
- solace_agent_mesh/cli/commands/eval_cmd.py +14 -0
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +53 -31
- solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +19 -8
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +80 -25
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +32 -10
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +74 -15
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +0 -2
- solace_agent_mesh/cli/commands/run_cmd.py +5 -3
- solace_agent_mesh/cli/utils.py +68 -12
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-vY5eu2lI.js +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/client-BeBkzgWW.js +25 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Bjys1KQs.js +339 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-CE0AeXyK.js +395 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -2
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -3
- solace_agent_mesh/common/utils/embeds/resolver.py +1 -0
- solace_agent_mesh/config_portal/backend/common.py +2 -2
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-bFMKlzKf.js +98 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d845808d.js → manifest-89db7c30.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/evaluation/message_organizer.py +35 -56
- solace_agent_mesh/evaluation/run.py +26 -5
- solace_agent_mesh/evaluation/subscriber.py +35 -10
- solace_agent_mesh/evaluation/summary_builder.py +27 -34
- solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +676 -0
- solace_agent_mesh/gateway/http_sse/alembic/env.py +85 -0
- solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/b1c2d3e4f5g6_add_database_indexes.py +83 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/d5b3f8f2e9a0_create_initial_database.py +58 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +147 -0
- solace_agent_mesh/gateway/http_sse/api/__init__.py +11 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +9 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/session_controller.py +355 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +279 -0
- solace_agent_mesh/gateway/http_sse/api/controllers/user_controller.py +35 -0
- solace_agent_mesh/gateway/http_sse/api/dto/__init__.py +10 -0
- solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +37 -0
- solace_agent_mesh/gateway/http_sse/api/dto/requests/session_requests.py +49 -0
- solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +66 -0
- solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +43 -0
- solace_agent_mesh/gateway/http_sse/api/dto/responses/session_responses.py +68 -0
- solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +74 -0
- solace_agent_mesh/gateway/http_sse/app.py +31 -1
- solace_agent_mesh/gateway/http_sse/application/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/application/services/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/application/services/session_service.py +135 -0
- solace_agent_mesh/gateway/http_sse/component.py +224 -62
- solace_agent_mesh/gateway/http_sse/dependencies.py +142 -39
- solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/domain/entities/session.py +90 -0
- solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +54 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +123 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +16 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +119 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +31 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +12 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +3 -0
- solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +174 -0
- solace_agent_mesh/gateway/http_sse/main.py +289 -85
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +121 -54
- solace_agent_mesh/gateway/http_sse/routers/config.py +3 -1
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +83 -2
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +7 -7
- solace_agent_mesh/gateway/http_sse/session_manager.py +64 -30
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +9 -0
- solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
- solace_agent_mesh/gateway/http_sse/shared/enums.py +45 -0
- solace_agent_mesh/gateway/http_sse/shared/types.py +45 -0
- solace_agent_mesh/templates/shared_config.yaml +4 -5
- solace_agent_mesh/templates/webui.yaml +8 -10
- {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/METADATA +5 -3
- {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/RECORD +130 -91
- solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1756992446316.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-BmF2l6vg.js +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/client-D881Dttc.js +0 -49
- solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +0 -699
- solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-Bym6YkMd.js +0 -98
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +0 -85
- solace_agent_mesh/gateway/http_sse/routers/users.py +0 -59
- /solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js.LICENSE.txt → main.08d30374.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,64 +1,118 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from fastapi
|
|
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
|
|
20
|
+
from ...common import a2a
|
|
21
|
+
from ...gateway.http_sse import dependencies
|
|
21
22
|
from ...gateway.http_sse.routers import (
|
|
22
23
|
agents,
|
|
23
|
-
tasks,
|
|
24
|
-
sse,
|
|
25
|
-
config,
|
|
26
24
|
artifacts,
|
|
27
|
-
visualization,
|
|
28
|
-
sessions,
|
|
29
|
-
people,
|
|
30
25
|
auth,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
JSONRPCError,
|
|
37
|
-
InternalError,
|
|
26
|
+
config,
|
|
27
|
+
people,
|
|
28
|
+
sse,
|
|
29
|
+
tasks,
|
|
30
|
+
visualization,
|
|
38
31
|
)
|
|
39
|
-
from ...common import a2a
|
|
40
32
|
|
|
41
|
-
|
|
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="
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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": "
|
|
227
|
-
"error_type": "
|
|
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.
|
|
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":
|
|
240
|
-
"email": email_from_auth,
|
|
241
|
-
"name":
|
|
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:
|
|
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
|
-
|
|
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.
|
|
268
|
-
"AuthMiddleware:
|
|
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
495
|
app.include_router(agents.router, prefix=api_prefix, tags=["Agents"])
|
|
315
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
595
|
+
error_response = {"detail": str(exc.detail)}
|
|
380
596
|
|
|
381
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
)
|