solace-agent-mesh 1.5.1__py3-none-any.whl → 1.6.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.
- solace_agent_mesh/agent/adk/callbacks.py +0 -5
- solace_agent_mesh/agent/adk/models/lite_llm.py +123 -8
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +245 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +213 -31
- solace_agent_mesh/agent/proxies/__init__.py +0 -0
- solace_agent_mesh/agent/proxies/a2a/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/a2a/app.py +55 -0
- solace_agent_mesh/agent/proxies/a2a/component.py +1115 -0
- solace_agent_mesh/agent/proxies/a2a/config.py +140 -0
- solace_agent_mesh/agent/proxies/a2a/oauth_token_cache.py +104 -0
- solace_agent_mesh/agent/proxies/base/__init__.py +3 -0
- solace_agent_mesh/agent/proxies/base/app.py +99 -0
- solace_agent_mesh/agent/proxies/base/component.py +650 -0
- solace_agent_mesh/agent/proxies/base/config.py +85 -0
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +17 -0
- solace_agent_mesh/agent/sac/app.py +58 -5
- solace_agent_mesh/agent/sac/component.py +238 -75
- solace_agent_mesh/agent/sac/task_execution_context.py +46 -0
- solace_agent_mesh/agent/tools/audio_tools.py +125 -8
- solace_agent_mesh/agent/tools/web_tools.py +10 -5
- solace_agent_mesh/agent/utils/artifact_helpers.py +141 -3
- solace_agent_mesh/assets/docs/404.html +3 -3
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/77cf947d.48cb18a2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.570c057b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{ad71b5ed.60668e9e.js → ad71b5ed.af3ecfd1.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/ceb2a7a6.5d92d7d0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{da0b5bad.9d369087.js → da0b5bad.d08a9466.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.e98d12a1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{e3d9abda.2b916f9e.js → e3d9abda.6b9493d0.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js → main.b12eac43.js} +2 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.e268214e.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +15 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +262 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +31 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +135 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +6 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +5 -5
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +4 -4
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +6 -5
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +100 -3
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +3 -3
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +3 -3
- solace_agent_mesh/assets/docs/lunr-index-1761248203150.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1761248203150.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +2 -69
- solace_agent_mesh/cli/commands/eval_cmd.py +11 -49
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +0 -5
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +10 -12
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +9 -61
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +9 -49
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +1 -2
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-DwrxZE0E.js → authCallback-BTf6dqwp.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/{client-DarGQzyw.js → client-CaY59VuC.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-B32noGmR.js +342 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{vendor-BKIeiHj_.js → vendor-BEmvJSYz.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/common/a2a/__init__.py +24 -0
- solace_agent_mesh/common/a2a/artifact.py +39 -0
- solace_agent_mesh/common/a2a/events.py +29 -0
- solace_agent_mesh/common/a2a/message.py +68 -0
- solace_agent_mesh/common/a2a/protocol.py +151 -1
- solace_agent_mesh/common/agent_registry.py +83 -3
- solace_agent_mesh/common/constants.py +3 -1
- solace_agent_mesh/common/sac/sam_component_base.py +383 -4
- solace_agent_mesh/common/utils/pydantic_utils.py +12 -0
- solace_agent_mesh/config_portal/backend/common.py +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +98 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-44d62be6.js → manifest-61038fc6.js} +1 -1
- solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
- solace_agent_mesh/evaluation/evaluator.py +128 -104
- solace_agent_mesh/evaluation/message_organizer.py +116 -110
- solace_agent_mesh/evaluation/report_data_processor.py +84 -86
- solace_agent_mesh/evaluation/report_generator.py +73 -79
- solace_agent_mesh/evaluation/run.py +421 -235
- solace_agent_mesh/evaluation/shared/__init__.py +92 -0
- solace_agent_mesh/evaluation/shared/constants.py +47 -0
- solace_agent_mesh/evaluation/shared/exceptions.py +50 -0
- solace_agent_mesh/evaluation/shared/helpers.py +35 -0
- solace_agent_mesh/evaluation/shared/test_case_loader.py +167 -0
- solace_agent_mesh/evaluation/shared/test_suite_loader.py +280 -0
- solace_agent_mesh/evaluation/subscriber.py +111 -232
- solace_agent_mesh/evaluation/summary_builder.py +227 -117
- solace_agent_mesh/gateway/base/app.py +16 -1
- solace_agent_mesh/gateway/base/component.py +112 -39
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py +70 -0
- solace_agent_mesh/gateway/http_sse/component.py +99 -3
- solace_agent_mesh/gateway/http_sse/dependencies.py +4 -4
- solace_agent_mesh/gateway/http_sse/main.py +1 -0
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +12 -13
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +15 -18
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +25 -18
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +30 -26
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +35 -44
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +95 -203
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +4 -3
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +33 -41
- solace_agent_mesh/gateway/http_sse/routers/users.py +47 -1
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +17 -11
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +4 -4
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +51 -43
- solace_agent_mesh/gateway/http_sse/services/session_service.py +20 -20
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +8 -8
- solace_agent_mesh/gateway/http_sse/shared/base_repository.py +45 -71
- solace_agent_mesh/gateway/http_sse/shared/types.py +0 -18
- solace_agent_mesh/templates/gateway_config_template.yaml +0 -5
- solace_agent_mesh/templates/logging_config_template.ini +10 -6
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +0 -3
- solace_agent_mesh/templates/shared_config.yaml +40 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/METADATA +47 -21
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/RECORD +166 -145
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.e49689dd.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.39d5851d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/71da7b71.804d6567.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/77cf947d.64c9bd6c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9e9d0a82.dd810042.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/db924877.cbc66f02.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/de915948.139b4b9c.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.582a78ca.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.5766a13d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.9c0297a6.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/runtime~main.18dc45dd.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1760121512891.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1760121512891.json +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/main-2nd1gbaH.js +0 -339
- solace_agent_mesh/client/webui/frontend/static/assets/main-DoKXctCM.css +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-BNuqpWDc.js +0 -98
- solace_agent_mesh/evaluation/config_loader.py +0 -657
- solace_agent_mesh/evaluation/test_case_loader.py +0 -714
- /solace_agent_mesh/assets/docs/assets/js/{main.bd3c34f3.js.LICENSE.txt → main.b12eac43.js.LICENSE.txt} +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/WHEEL +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.5.1.dist-info → solace_agent_mesh-1.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -19,6 +19,7 @@ from ...common.a2a import (
|
|
|
19
19
|
|
|
20
20
|
log = logging.getLogger(__name__)
|
|
21
21
|
|
|
22
|
+
|
|
22
23
|
class BaseGatewayComponent(ComponentBase):
|
|
23
24
|
pass
|
|
24
25
|
|
|
@@ -244,6 +245,20 @@ class BaseGatewayApp(App):
|
|
|
244
245
|
)
|
|
245
246
|
},
|
|
246
247
|
]
|
|
248
|
+
|
|
249
|
+
# Add trust card subscription if trust manager is enabled
|
|
250
|
+
trust_config = resolved_app_config_block.get("trust_manager")
|
|
251
|
+
if trust_config and trust_config.get("enabled", False):
|
|
252
|
+
from ...common.a2a.protocol import get_trust_card_subscription_topic
|
|
253
|
+
|
|
254
|
+
trust_card_topic = get_trust_card_subscription_topic(self.namespace)
|
|
255
|
+
subscriptions.append({"topic": trust_card_topic})
|
|
256
|
+
log.info(
|
|
257
|
+
"Trust Manager enabled for gateway '%s', added trust card subscription: %s",
|
|
258
|
+
self.gateway_id,
|
|
259
|
+
trust_card_topic,
|
|
260
|
+
)
|
|
261
|
+
|
|
247
262
|
log.info(
|
|
248
263
|
"Generated Solace subscriptions for gateway '%s': %s",
|
|
249
264
|
self.gateway_id,
|
|
@@ -272,7 +287,7 @@ class BaseGatewayApp(App):
|
|
|
272
287
|
broker_config["queue_name"] = (
|
|
273
288
|
f"{self.namespace.strip('/')}/q/gdk/gateway/{self.gateway_id}"
|
|
274
289
|
)
|
|
275
|
-
broker_config["temporary_queue"] = True
|
|
290
|
+
broker_config["temporary_queue"] = modified_app_info.get("broker", {}).get("temporary_queue", True)
|
|
276
291
|
log.debug(
|
|
277
292
|
"Injected broker settings for gateway '%s': %s",
|
|
278
293
|
self.gateway_id,
|
|
@@ -282,6 +282,9 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
282
282
|
"user_id_for_a2a", user_identity.get("id")
|
|
283
283
|
)
|
|
284
284
|
|
|
285
|
+
system_purpose = self.get_config("system_purpose", "")
|
|
286
|
+
response_format = self.get_config("response_format", "")
|
|
287
|
+
|
|
285
288
|
if not a2a_session_id:
|
|
286
289
|
a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
|
|
287
290
|
log.warning(
|
|
@@ -291,7 +294,11 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
291
294
|
)
|
|
292
295
|
external_request_context["a2a_session_id"] = a2a_session_id
|
|
293
296
|
|
|
294
|
-
a2a_metadata = {
|
|
297
|
+
a2a_metadata = {
|
|
298
|
+
"agent_name": target_agent_name,
|
|
299
|
+
"system_purpose": system_purpose,
|
|
300
|
+
"response_format": response_format,
|
|
301
|
+
}
|
|
295
302
|
invoked_artifacts = external_request_context.get("invoked_with_artifacts")
|
|
296
303
|
if invoked_artifacts:
|
|
297
304
|
a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
|
|
@@ -336,6 +343,37 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
336
343
|
if user_config:
|
|
337
344
|
user_properties["a2aUserConfig"] = user_config
|
|
338
345
|
|
|
346
|
+
# Enterprise feature: Add signed user claims if trust manager available
|
|
347
|
+
if hasattr(self, "trust_manager") and self.trust_manager:
|
|
348
|
+
log.debug(
|
|
349
|
+
"%s Attempting to sign user claims for task %s",
|
|
350
|
+
log_id_prefix,
|
|
351
|
+
task_id,
|
|
352
|
+
)
|
|
353
|
+
try:
|
|
354
|
+
auth_token = self.trust_manager.sign_user_claims(
|
|
355
|
+
user_info=user_identity, task_id=task_id
|
|
356
|
+
)
|
|
357
|
+
user_properties["authToken"] = auth_token
|
|
358
|
+
log.debug(
|
|
359
|
+
"%s Successfully signed user claims for task %s",
|
|
360
|
+
log_id_prefix,
|
|
361
|
+
task_id,
|
|
362
|
+
)
|
|
363
|
+
except Exception as e:
|
|
364
|
+
log.error(
|
|
365
|
+
"%s Failed to sign user claims for task %s: %s",
|
|
366
|
+
log_id_prefix,
|
|
367
|
+
task_id,
|
|
368
|
+
e,
|
|
369
|
+
)
|
|
370
|
+
# Continue without token - enterprise feature is optional
|
|
371
|
+
else:
|
|
372
|
+
log.debug(
|
|
373
|
+
"%s Trust Manager not available, proceeding without authentication token",
|
|
374
|
+
log_id_prefix,
|
|
375
|
+
)
|
|
376
|
+
|
|
339
377
|
user_properties["replyTo"] = a2a.get_gateway_response_topic(
|
|
340
378
|
self.namespace, self.gateway_id, task_id
|
|
341
379
|
)
|
|
@@ -359,49 +397,55 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
359
397
|
)
|
|
360
398
|
return task_id
|
|
361
399
|
|
|
362
|
-
def
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if not original_broker_message:
|
|
366
|
-
log.warning(
|
|
367
|
-
"%s Received MESSAGE event with no data. Ignoring.",
|
|
368
|
-
self.log_identifier,
|
|
369
|
-
)
|
|
370
|
-
return
|
|
400
|
+
def _handle_message(self, message: SolaceMessage, topic: str) -> None:
|
|
401
|
+
"""
|
|
402
|
+
Override to use queue-based pattern instead of direct async.
|
|
371
403
|
|
|
372
|
-
|
|
373
|
-
|
|
404
|
+
Gateway uses an internal queue for message processing to ensure
|
|
405
|
+
strict ordering and backpressure handling.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
message: The Solace message
|
|
409
|
+
topic: The topic the message was received on
|
|
410
|
+
"""
|
|
411
|
+
log.debug(
|
|
412
|
+
"%s Received SolaceMessage on topic: %s. Bridging to internal queue.",
|
|
413
|
+
self.log_identifier,
|
|
414
|
+
topic,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
msg_data_for_processor = {
|
|
419
|
+
"topic": topic,
|
|
420
|
+
"payload": message.get_payload(),
|
|
421
|
+
"user_properties": message.get_user_properties(),
|
|
422
|
+
"_original_broker_message": message,
|
|
423
|
+
}
|
|
424
|
+
self.internal_event_queue.put_nowait(msg_data_for_processor)
|
|
425
|
+
except queue.Full:
|
|
426
|
+
log.error(
|
|
427
|
+
"%s Internal event queue full. Cannot bridge message.",
|
|
374
428
|
self.log_identifier,
|
|
375
|
-
original_broker_message.get_topic(),
|
|
376
429
|
)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
"user_properties": original_broker_message.get_user_properties(),
|
|
382
|
-
"_original_broker_message": original_broker_message,
|
|
383
|
-
}
|
|
384
|
-
self.internal_event_queue.put_nowait(msg_data_for_processor)
|
|
385
|
-
except queue.Full:
|
|
386
|
-
log.error(
|
|
387
|
-
"%s Internal event queue full. Cannot bridge message. NACKing.",
|
|
388
|
-
self.log_identifier,
|
|
389
|
-
)
|
|
390
|
-
original_broker_message.call_negative_acknowledgements()
|
|
391
|
-
except Exception as e:
|
|
392
|
-
log.exception(
|
|
393
|
-
"%s Error bridging message to internal queue: %s. NACKing.",
|
|
394
|
-
self.log_identifier,
|
|
395
|
-
e,
|
|
396
|
-
)
|
|
397
|
-
original_broker_message.call_negative_acknowledgements()
|
|
398
|
-
else:
|
|
399
|
-
log.debug(
|
|
400
|
-
"%s Received non-MESSAGE event type: %s. Passing to super.",
|
|
430
|
+
raise
|
|
431
|
+
except Exception as e:
|
|
432
|
+
log.exception(
|
|
433
|
+
"%s Error bridging message to internal queue: %s",
|
|
401
434
|
self.log_identifier,
|
|
402
|
-
|
|
435
|
+
e,
|
|
403
436
|
)
|
|
404
|
-
|
|
437
|
+
raise
|
|
438
|
+
|
|
439
|
+
async def _handle_message_async(self, message, topic: str) -> None:
|
|
440
|
+
"""
|
|
441
|
+
Not used by gateway - we override _handle_message() instead.
|
|
442
|
+
|
|
443
|
+
This is here to satisfy the abstract method requirement, but the
|
|
444
|
+
gateway uses the queue-based pattern via _handle_message() override.
|
|
445
|
+
"""
|
|
446
|
+
raise NotImplementedError(
|
|
447
|
+
"Gateway uses queue-based message handling via _handle_message() override"
|
|
448
|
+
)
|
|
405
449
|
|
|
406
450
|
async def _handle_resolved_signals(
|
|
407
451
|
self,
|
|
@@ -1113,6 +1157,9 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
1113
1157
|
|
|
1114
1158
|
async def _async_setup_and_run(self) -> None:
|
|
1115
1159
|
"""Main async logic for the gateway component."""
|
|
1160
|
+
# Call base class to initialize Trust Manager
|
|
1161
|
+
await super()._async_setup_and_run()
|
|
1162
|
+
|
|
1116
1163
|
log.info(
|
|
1117
1164
|
"%s Starting _start_listener() to initiate external platform connection.",
|
|
1118
1165
|
self.log_identifier,
|
|
@@ -1127,6 +1174,17 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
1127
1174
|
|
|
1128
1175
|
def _pre_async_cleanup(self) -> None:
|
|
1129
1176
|
"""Pre-cleanup actions for the gateway component."""
|
|
1177
|
+
# Cleanup Trust Manager if present (ENTERPRISE FEATURE)
|
|
1178
|
+
if self.trust_manager:
|
|
1179
|
+
try:
|
|
1180
|
+
log.info("%s Cleaning up Trust Manager...", self.log_identifier)
|
|
1181
|
+
self.trust_manager.cleanup(self.cancel_timer)
|
|
1182
|
+
log.info("%s Trust Manager cleanup complete", self.log_identifier)
|
|
1183
|
+
except Exception as e:
|
|
1184
|
+
log.error(
|
|
1185
|
+
"%s Error during Trust Manager cleanup: %s", self.log_identifier, e
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1130
1188
|
log.info("%s Calling _stop_listener()...", self.log_identifier)
|
|
1131
1189
|
self._stop_listener()
|
|
1132
1190
|
|
|
@@ -1177,6 +1235,13 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
1177
1235
|
processed_successfully = await self._handle_discovery_message(
|
|
1178
1236
|
payload
|
|
1179
1237
|
)
|
|
1238
|
+
elif (
|
|
1239
|
+
hasattr(self, "trust_manager")
|
|
1240
|
+
and self.trust_manager
|
|
1241
|
+
and self.trust_manager.is_trust_card_topic(topic)
|
|
1242
|
+
):
|
|
1243
|
+
await self.trust_manager.handle_trust_card_message(payload, topic)
|
|
1244
|
+
processed_successfully = True
|
|
1180
1245
|
elif a2a.topic_matches_subscription(
|
|
1181
1246
|
topic,
|
|
1182
1247
|
a2a.get_gateway_response_subscription_topic(
|
|
@@ -1321,6 +1386,14 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
1321
1386
|
) -> None:
|
|
1322
1387
|
pass
|
|
1323
1388
|
|
|
1389
|
+
def _get_component_id(self) -> str:
|
|
1390
|
+
"""Returns the gateway ID as the component identifier."""
|
|
1391
|
+
return self.gateway_id
|
|
1392
|
+
|
|
1393
|
+
def _get_component_type(self) -> str:
|
|
1394
|
+
"""Returns 'gateway' as the component type."""
|
|
1395
|
+
return "gateway"
|
|
1396
|
+
|
|
1324
1397
|
def invoke(self, message, data):
|
|
1325
1398
|
if isinstance(message, SolaceMessage):
|
|
1326
1399
|
message.call_acknowledgements()
|
solace_agent_mesh/gateway/http_sse/alembic/versions/20251015_add_session_performance_indexes.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""add performance indexes for query optimization
|
|
2
|
+
|
|
3
|
+
Revision ID: 20251015_session_idx
|
|
4
|
+
Revises: 98882922fa59
|
|
5
|
+
Create Date: 2025-10-15
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
revision: str = "20251015_session_idx"
|
|
15
|
+
down_revision: str | Sequence[str] | None = "98882922fa59"
|
|
16
|
+
branch_labels: str | Sequence[str] | None = None
|
|
17
|
+
depends_on: str | Sequence[str] | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
"""Add composite indexes for optimal query performance."""
|
|
22
|
+
|
|
23
|
+
op.create_index("ix_sessions_user_id", "sessions", ["user_id"], unique=False)
|
|
24
|
+
|
|
25
|
+
op.create_index(
|
|
26
|
+
"ix_sessions_user_updated",
|
|
27
|
+
"sessions",
|
|
28
|
+
["user_id", sa.text("updated_time DESC")],
|
|
29
|
+
unique=False,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
op.create_index(
|
|
33
|
+
"ix_chat_tasks_session_user_created",
|
|
34
|
+
"chat_tasks",
|
|
35
|
+
["session_id", "user_id", "created_time"],
|
|
36
|
+
unique=False,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
op.create_index(
|
|
40
|
+
"ix_tasks_user_start_time",
|
|
41
|
+
"tasks",
|
|
42
|
+
["user_id", sa.text("start_time DESC")],
|
|
43
|
+
unique=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
op.create_index(
|
|
47
|
+
"ix_task_events_task_created",
|
|
48
|
+
"task_events",
|
|
49
|
+
["task_id", "created_time"],
|
|
50
|
+
unique=False,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
op.drop_index("ix_tasks_initial_request_text", table_name="tasks")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def downgrade() -> None:
|
|
57
|
+
"""Remove performance indexes."""
|
|
58
|
+
|
|
59
|
+
op.create_index(
|
|
60
|
+
op.f("ix_tasks_initial_request_text"),
|
|
61
|
+
"tasks",
|
|
62
|
+
["initial_request_text"],
|
|
63
|
+
unique=False,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
op.drop_index("ix_task_events_task_created", table_name="task_events")
|
|
67
|
+
op.drop_index("ix_tasks_user_start_time", table_name="tasks")
|
|
68
|
+
op.drop_index("ix_chat_tasks_session_user_created", table_name="chat_tasks")
|
|
69
|
+
op.drop_index("ix_sessions_user_updated", table_name="sessions")
|
|
70
|
+
op.drop_index("ix_sessions_user_id", table_name="sessions")
|
|
@@ -146,6 +146,27 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
146
146
|
timer_id=self._sse_cleanup_timer_id,
|
|
147
147
|
interval_ms=cleanup_interval_sec * 1000,
|
|
148
148
|
)
|
|
149
|
+
|
|
150
|
+
# Set up health check timer for agent registry
|
|
151
|
+
from ...common.constants import HEALTH_CHECK_INTERVAL_SECONDS
|
|
152
|
+
self.health_check_timer_id = f"agent_health_check_{self.gateway_id}"
|
|
153
|
+
health_check_interval_seconds = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
|
|
154
|
+
if health_check_interval_seconds > 0:
|
|
155
|
+
log.info(
|
|
156
|
+
"%s Scheduling agent health check every %d seconds.",
|
|
157
|
+
self.log_identifier,
|
|
158
|
+
health_check_interval_seconds,
|
|
159
|
+
)
|
|
160
|
+
self.add_timer(
|
|
161
|
+
delay_ms=health_check_interval_seconds * 1000,
|
|
162
|
+
timer_id=self.health_check_timer_id,
|
|
163
|
+
interval_ms=health_check_interval_seconds * 1000,
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
log.warning(
|
|
167
|
+
"%s Agent health check interval not configured or invalid, health checks will not run periodically.",
|
|
168
|
+
self.log_identifier,
|
|
169
|
+
)
|
|
149
170
|
|
|
150
171
|
session_config = self._resolve_session_config()
|
|
151
172
|
if session_config.get("type") == "sql":
|
|
@@ -265,6 +286,10 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
265
286
|
log.debug("%s SSE buffer cleanup timer triggered.", self.log_identifier)
|
|
266
287
|
self.sse_event_buffer.cleanup_stale_buffers()
|
|
267
288
|
return
|
|
289
|
+
elif event.data.get("timer_id") == self.health_check_timer_id:
|
|
290
|
+
log.debug("%s Agent health check timer triggered.", self.log_identifier)
|
|
291
|
+
self._check_agent_health()
|
|
292
|
+
return
|
|
268
293
|
|
|
269
294
|
if timer_id == self._data_retention_timer_id:
|
|
270
295
|
log.debug("%s Data retention cleanup timer triggered.", self.log_identifier)
|
|
@@ -354,7 +379,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
354
379
|
),
|
|
355
380
|
"retry_interval": main_broker_config.get("retry_interval"),
|
|
356
381
|
"retry_count": main_broker_config.get("retry_count"),
|
|
357
|
-
"temporary_queue": True,
|
|
382
|
+
"temporary_queue": main_broker_config.get("temporary_queue", True),
|
|
358
383
|
},
|
|
359
384
|
}
|
|
360
385
|
|
|
@@ -486,7 +511,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
486
511
|
),
|
|
487
512
|
"retry_interval": main_broker_config.get("retry_interval"),
|
|
488
513
|
"retry_count": main_broker_config.get("retry_count"),
|
|
489
|
-
"temporary_queue": True,
|
|
514
|
+
"temporary_queue": main_broker_config.get("temporary_queue", True),
|
|
490
515
|
},
|
|
491
516
|
}
|
|
492
517
|
|
|
@@ -1197,7 +1222,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1197
1222
|
app=self.fastapi_app,
|
|
1198
1223
|
host=self.fastapi_host,
|
|
1199
1224
|
port=port,
|
|
1200
|
-
log_level="
|
|
1225
|
+
log_level="warning",
|
|
1201
1226
|
lifespan="on",
|
|
1202
1227
|
ssl_keyfile=self.ssl_keyfile,
|
|
1203
1228
|
ssl_certfile=self.ssl_certfile,
|
|
@@ -1404,6 +1429,7 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1404
1429
|
self.data_retention_service = None
|
|
1405
1430
|
log.info("%s Data retention service cleaned up.", self.log_identifier)
|
|
1406
1431
|
|
|
1432
|
+
self.cancel_timer(self.health_check_timer_id)
|
|
1407
1433
|
log.info("%s Cleaning up visualization resources...", self.log_identifier)
|
|
1408
1434
|
if self._visualization_message_queue:
|
|
1409
1435
|
self._visualization_message_queue.put(None)
|
|
@@ -1729,6 +1755,76 @@ class WebUIBackendComponent(BaseGatewayComponent):
|
|
|
1729
1755
|
|
|
1730
1756
|
def get_agent_registry(self) -> AgentRegistry:
|
|
1731
1757
|
return self.agent_registry
|
|
1758
|
+
|
|
1759
|
+
def _check_agent_health(self):
|
|
1760
|
+
"""
|
|
1761
|
+
Checks the health of peer agents and de-registers unresponsive ones.
|
|
1762
|
+
This is called periodically by the health check timer.
|
|
1763
|
+
Uses TTL-based expiration to determine if an agent is unresponsive.
|
|
1764
|
+
"""
|
|
1765
|
+
|
|
1766
|
+
log.debug("%s Performing agent health check...", self.log_identifier)
|
|
1767
|
+
|
|
1768
|
+
# Get TTL from configuration or use default from constants
|
|
1769
|
+
from ...common.constants import HEALTH_CHECK_TTL_SECONDS, HEALTH_CHECK_INTERVAL_SECONDS
|
|
1770
|
+
ttl_seconds = self.get_config("agent_health_check_ttl_seconds", HEALTH_CHECK_TTL_SECONDS)
|
|
1771
|
+
health_check_interval = self.get_config("agent_health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS)
|
|
1772
|
+
|
|
1773
|
+
log.debug(
|
|
1774
|
+
"%s Health check configuration: interval=%d seconds, TTL=%d seconds",
|
|
1775
|
+
self.log_identifier,
|
|
1776
|
+
health_check_interval,
|
|
1777
|
+
ttl_seconds
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
# Validate configuration values
|
|
1781
|
+
if ttl_seconds <= 0 or health_check_interval <= 0 or ttl_seconds < health_check_interval:
|
|
1782
|
+
log.error(
|
|
1783
|
+
"%s agent_health_check_ttl_seconds (%d) and agent_health_check_interval_seconds (%d) must be positive and TTL must be greater than interval.",
|
|
1784
|
+
self.log_identifier,
|
|
1785
|
+
ttl_seconds,
|
|
1786
|
+
health_check_interval
|
|
1787
|
+
)
|
|
1788
|
+
raise ValueError(f"Invalid health check configuration. agent_health_check_ttl_seconds ({ttl_seconds}) and agent_health_check_interval_seconds ({health_check_interval}) must be positive and TTL must be greater than interval.")
|
|
1789
|
+
|
|
1790
|
+
# Get all agent names from the registry
|
|
1791
|
+
agent_names = self.agent_registry.get_agent_names()
|
|
1792
|
+
total_agents = len(agent_names)
|
|
1793
|
+
agents_to_deregister = []
|
|
1794
|
+
|
|
1795
|
+
log.debug("%s Checking health of %d peer agents", self.log_identifier, total_agents)
|
|
1796
|
+
|
|
1797
|
+
for agent_name in agent_names:
|
|
1798
|
+
# Check if the agent's TTL has expired
|
|
1799
|
+
is_expired, time_since_last_seen = self.agent_registry.check_ttl_expired(agent_name, ttl_seconds)
|
|
1800
|
+
|
|
1801
|
+
if is_expired:
|
|
1802
|
+
log.warning(
|
|
1803
|
+
"%s Agent '%s' TTL has expired. De-registering. Time since last seen: %d seconds (TTL: %d seconds)",
|
|
1804
|
+
self.log_identifier,
|
|
1805
|
+
agent_name,
|
|
1806
|
+
time_since_last_seen,
|
|
1807
|
+
ttl_seconds
|
|
1808
|
+
)
|
|
1809
|
+
agents_to_deregister.append(agent_name)
|
|
1810
|
+
|
|
1811
|
+
# De-register unresponsive agents
|
|
1812
|
+
for agent_name in agents_to_deregister:
|
|
1813
|
+
self._deregister_agent(agent_name)
|
|
1814
|
+
|
|
1815
|
+
log.info(
|
|
1816
|
+
"%s Agent health check completed. Total agents: %d, De-registered: %d",
|
|
1817
|
+
self.log_identifier,
|
|
1818
|
+
total_agents,
|
|
1819
|
+
len(agents_to_deregister)
|
|
1820
|
+
)
|
|
1821
|
+
|
|
1822
|
+
def _deregister_agent(self, agent_name: str):
|
|
1823
|
+
"""
|
|
1824
|
+
De-registers an agent from the registry and publishes a de-registration event.
|
|
1825
|
+
"""
|
|
1826
|
+
# Remove from registry
|
|
1827
|
+
self.agent_registry.remove_agent(agent_name)
|
|
1732
1828
|
|
|
1733
1829
|
def get_sse_manager(self) -> SSEManager:
|
|
1734
1830
|
return self.sse_manager
|
|
@@ -243,10 +243,10 @@ def get_people_service(
|
|
|
243
243
|
return PeopleService(identity_service=identity_service)
|
|
244
244
|
|
|
245
245
|
|
|
246
|
-
def get_task_repository(
|
|
246
|
+
def get_task_repository() -> ITaskRepository:
|
|
247
247
|
"""FastAPI dependency to get an instance of TaskRepository."""
|
|
248
248
|
log.debug("[Dependencies] get_task_repository called")
|
|
249
|
-
return TaskRepository(
|
|
249
|
+
return TaskRepository()
|
|
250
250
|
|
|
251
251
|
|
|
252
252
|
def get_feedback_service(
|
|
@@ -525,9 +525,9 @@ def get_session_validator(
|
|
|
525
525
|
try:
|
|
526
526
|
db = SessionLocal()
|
|
527
527
|
try:
|
|
528
|
-
session_repository = SessionRepository(
|
|
528
|
+
session_repository = SessionRepository()
|
|
529
529
|
session_domain = session_repository.find_user_session(
|
|
530
|
-
session_id, user_id
|
|
530
|
+
db, session_id, user_id
|
|
531
531
|
)
|
|
532
532
|
return session_domain is not None
|
|
533
533
|
finally:
|
|
@@ -107,6 +107,7 @@ def _extract_user_identifier(user_info: dict) -> str:
|
|
|
107
107
|
or user_info.get("email")
|
|
108
108
|
or user_info.get("name")
|
|
109
109
|
or user_info.get("azp")
|
|
110
|
+
or user_info.get("user_id") # internal /user_info endpoint format maps identifier to user_id
|
|
110
111
|
)
|
|
111
112
|
|
|
112
113
|
if user_identifier and user_identifier.lower() == "unknown":
|
|
@@ -16,12 +16,9 @@ from .models import ChatTaskModel
|
|
|
16
16
|
class ChatTaskRepository(IChatTaskRepository):
|
|
17
17
|
"""SQLAlchemy implementation of chat task repository."""
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
self.db = db
|
|
21
|
-
|
|
22
|
-
def save(self, task: ChatTask) -> ChatTask:
|
|
19
|
+
def save(self, session: DBSession, task: ChatTask) -> ChatTask:
|
|
23
20
|
"""Save or update a chat task (upsert)."""
|
|
24
|
-
existing =
|
|
21
|
+
existing = session.query(ChatTaskModel).filter(
|
|
25
22
|
ChatTaskModel.id == task.id
|
|
26
23
|
).first()
|
|
27
24
|
|
|
@@ -43,12 +40,12 @@ class ChatTaskRepository(IChatTaskRepository):
|
|
|
43
40
|
created_time=task.created_time,
|
|
44
41
|
updated_time=task.updated_time
|
|
45
42
|
)
|
|
46
|
-
|
|
43
|
+
session.add(model)
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
session.flush()
|
|
49
46
|
|
|
50
47
|
# Reload to get updated values
|
|
51
|
-
model =
|
|
48
|
+
model = session.query(ChatTaskModel).filter(
|
|
52
49
|
ChatTaskModel.id == task.id
|
|
53
50
|
).first()
|
|
54
51
|
|
|
@@ -56,11 +53,12 @@ class ChatTaskRepository(IChatTaskRepository):
|
|
|
56
53
|
|
|
57
54
|
def find_by_session(
|
|
58
55
|
self,
|
|
56
|
+
session: DBSession,
|
|
59
57
|
session_id: SessionId,
|
|
60
58
|
user_id: UserId
|
|
61
59
|
) -> List[ChatTask]:
|
|
62
60
|
"""Find all tasks for a session."""
|
|
63
|
-
models =
|
|
61
|
+
models = session.query(ChatTaskModel).filter(
|
|
64
62
|
ChatTaskModel.session_id == session_id,
|
|
65
63
|
ChatTaskModel.user_id == user_id
|
|
66
64
|
).order_by(ChatTaskModel.created_time.asc()).all()
|
|
@@ -69,23 +67,24 @@ class ChatTaskRepository(IChatTaskRepository):
|
|
|
69
67
|
|
|
70
68
|
def find_by_id(
|
|
71
69
|
self,
|
|
70
|
+
session: DBSession,
|
|
72
71
|
task_id: str,
|
|
73
72
|
user_id: UserId
|
|
74
73
|
) -> Optional[ChatTask]:
|
|
75
74
|
"""Find a specific task."""
|
|
76
|
-
model =
|
|
75
|
+
model = session.query(ChatTaskModel).filter(
|
|
77
76
|
ChatTaskModel.id == task_id,
|
|
78
77
|
ChatTaskModel.user_id == user_id
|
|
79
78
|
).first()
|
|
80
79
|
|
|
81
80
|
return self._model_to_entity(model) if model else None
|
|
82
81
|
|
|
83
|
-
def delete_by_session(self, session_id: SessionId) -> bool:
|
|
82
|
+
def delete_by_session(self, session: DBSession, session_id: SessionId) -> bool:
|
|
84
83
|
"""Delete all tasks for a session."""
|
|
85
|
-
result =
|
|
84
|
+
result = session.query(ChatTaskModel).filter(
|
|
86
85
|
ChatTaskModel.session_id == session_id
|
|
87
86
|
).delete()
|
|
88
|
-
|
|
87
|
+
session.flush()
|
|
89
88
|
return result > 0
|
|
90
89
|
|
|
91
90
|
def _model_to_entity(self, model: ChatTaskModel) -> ChatTask:
|
|
@@ -12,10 +12,7 @@ from .models import FeedbackModel
|
|
|
12
12
|
class FeedbackRepository(IFeedbackRepository):
|
|
13
13
|
"""SQLAlchemy implementation of feedback repository."""
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
self.db = db
|
|
17
|
-
|
|
18
|
-
def save(self, feedback: Feedback) -> Feedback:
|
|
15
|
+
def save(self, session: DBSession, feedback: Feedback) -> Feedback:
|
|
19
16
|
"""Save feedback."""
|
|
20
17
|
model = FeedbackModel(
|
|
21
18
|
id=feedback.id,
|
|
@@ -26,12 +23,12 @@ class FeedbackRepository(IFeedbackRepository):
|
|
|
26
23
|
comment=feedback.comment,
|
|
27
24
|
created_time=feedback.created_time,
|
|
28
25
|
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
session.add(model)
|
|
27
|
+
session.flush()
|
|
28
|
+
session.refresh(model)
|
|
32
29
|
return self._model_to_entity(model)
|
|
33
30
|
|
|
34
|
-
def delete_feedback_older_than(self, cutoff_time_ms: int, batch_size: int) -> int:
|
|
31
|
+
def delete_feedback_older_than(self, session: DBSession, cutoff_time_ms: int, batch_size: int) -> int:
|
|
35
32
|
"""
|
|
36
33
|
Delete feedback records older than the cutoff time.
|
|
37
34
|
Uses batch deletion to avoid long-running transactions.
|
|
@@ -44,36 +41,36 @@ class FeedbackRepository(IFeedbackRepository):
|
|
|
44
41
|
Total number of feedback records deleted
|
|
45
42
|
"""
|
|
46
43
|
total_deleted = 0
|
|
47
|
-
|
|
44
|
+
|
|
48
45
|
while True:
|
|
49
46
|
# Find a batch of feedback IDs to delete
|
|
50
47
|
feedback_ids_to_delete = (
|
|
51
|
-
|
|
48
|
+
session.query(FeedbackModel.id)
|
|
52
49
|
.filter(FeedbackModel.created_time < cutoff_time_ms)
|
|
53
50
|
.limit(batch_size)
|
|
54
51
|
.all()
|
|
55
52
|
)
|
|
56
|
-
|
|
53
|
+
|
|
57
54
|
if not feedback_ids_to_delete:
|
|
58
55
|
break
|
|
59
|
-
|
|
56
|
+
|
|
60
57
|
# Extract IDs from the result tuples
|
|
61
58
|
ids = [feedback_id[0] for feedback_id in feedback_ids_to_delete]
|
|
62
|
-
|
|
59
|
+
|
|
63
60
|
# Delete this batch
|
|
64
61
|
deleted_count = (
|
|
65
|
-
|
|
62
|
+
session.query(FeedbackModel)
|
|
66
63
|
.filter(FeedbackModel.id.in_(ids))
|
|
67
64
|
.delete(synchronize_session=False)
|
|
68
65
|
)
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
|
|
67
|
+
session.commit()
|
|
71
68
|
total_deleted += deleted_count
|
|
72
|
-
|
|
69
|
+
|
|
73
70
|
# If we deleted fewer than batch_size, we're done
|
|
74
71
|
if deleted_count < batch_size:
|
|
75
72
|
break
|
|
76
|
-
|
|
73
|
+
|
|
77
74
|
return total_deleted
|
|
78
75
|
|
|
79
76
|
def _model_to_entity(self, model: FeedbackModel) -> Feedback:
|