solace-agent-mesh 0.2.4__py3-none-any.whl → 1.0.2__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/__init__.py +5 -0
- solace_agent_mesh/agent/adk/adk_llm.txt +93 -0
- solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
- solace_agent_mesh/agent/adk/callbacks.py +1716 -0
- solace_agent_mesh/agent/adk/filesystem_artifact_service.py +381 -0
- solace_agent_mesh/agent/adk/invocation_monitor.py +295 -0
- solace_agent_mesh/agent/adk/models/lite_llm.py +872 -0
- solace_agent_mesh/agent/adk/models/models_llm.txt +94 -0
- solace_agent_mesh/agent/adk/runner.py +357 -0
- solace_agent_mesh/agent/adk/services.py +240 -0
- solace_agent_mesh/agent/adk/setup.py +751 -0
- solace_agent_mesh/agent/adk/stream_parser.py +214 -0
- solace_agent_mesh/agent/adk/tool_wrapper.py +139 -0
- solace_agent_mesh/agent/agent_llm.txt +41 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +1444 -0
- solace_agent_mesh/agent/protocol/protocol_llm.txt +21 -0
- solace_agent_mesh/agent/sac/app.py +640 -0
- solace_agent_mesh/agent/sac/component.py +3496 -0
- solace_agent_mesh/agent/sac/patch_adk.py +111 -0
- solace_agent_mesh/agent/sac/sac_llm.txt +105 -0
- solace_agent_mesh/agent/sac/task_execution_context.py +185 -0
- solace_agent_mesh/agent/testing/__init__.py +3 -0
- solace_agent_mesh/agent/testing/debug_utils.py +135 -0
- solace_agent_mesh/agent/testing/testing_llm.txt +90 -0
- solace_agent_mesh/agent/tools/__init__.py +14 -0
- solace_agent_mesh/agent/tools/audio_tools.py +1622 -0
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +1954 -0
- solace_agent_mesh/agent/tools/builtin_data_analysis_tools.py +238 -0
- solace_agent_mesh/agent/tools/general_agent_tools.py +571 -0
- solace_agent_mesh/agent/tools/image_tools.py +1184 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +290 -0
- solace_agent_mesh/agent/tools/registry.py +36 -0
- solace_agent_mesh/agent/tools/test_tools.py +135 -0
- solace_agent_mesh/agent/tools/tool_definition.py +45 -0
- solace_agent_mesh/agent/tools/tools_llm.txt +104 -0
- solace_agent_mesh/agent/tools/web_tools.py +381 -0
- solace_agent_mesh/agent/utils/artifact_helpers.py +927 -0
- solace_agent_mesh/agent/utils/config_parser.py +47 -0
- solace_agent_mesh/agent/utils/context_helpers.py +60 -0
- solace_agent_mesh/agent/utils/utils_llm.txt +153 -0
- solace_agent_mesh/assets/docs/404.html +16 -0
- solace_agent_mesh/assets/docs/assets/css/styles.906a1503.css +1 -0
- solace_agent_mesh/assets/docs/assets/images/Solace_AI_Framework_With_Broker-85f0a306a9bcdd20b390b7a949f6d862.png +0 -0
- solace_agent_mesh/assets/docs/assets/images/sac-flows-80d5b603c6aafd33e87945680ce0abf3.png +0 -0
- solace_agent_mesh/assets/docs/assets/images/sac_parts_of_a_component-cb3d0424b1d0c17734c5435cca6b4082.png +0 -0
- solace_agent_mesh/assets/docs/assets/js/04989206.674a8007.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/0e682baa.79f0ab22.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1001.0182a8bd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1023fc19.015679ca.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1039.0bd46aa1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/149.b797a808.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/1523c6b4.91c7bc01.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/165.6a39807d.js.LICENSE.txt +9 -0
- solace_agent_mesh/assets/docs/assets/js/166ab619.7d97ccaf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js.LICENSE.txt +7 -0
- solace_agent_mesh/assets/docs/assets/js/1c6e87d2.a8c5ce5a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2130.ab9fd314.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/21ceee5f.614fa8dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2237.5e477fc6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2334.622a6395.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3219.adc1d663.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3624.b524e433.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/375.708d48db.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3834.b6cd790e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3d406171.f722eaf5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4250.95455b28.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/42b3f8d8.36090198.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4356.d169ab5b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/442a8107.5ba94b65.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4458.518e66fa.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4488.c7cc3442.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4494.6ee23046.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4855.fc4444b6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4866.22daefc0.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4950.ca4caeda.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4c2787c2.66ee00e9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5388.7a136447.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5607.081356f8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5864.b0d0e9de.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5b4258a4.bda20761.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5e95c892.558d5167.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6143.0a1464c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6395.e9c73649.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6796.51d2c9b7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6976.379be23b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6978.ee0b945c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7040.cb436723.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7195.412f418a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7280.3fb73bdb.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/768e31b0.a12673db.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7845.e33e7c4c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7900.69516146.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8356.8a379c04.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/85387663.6bf41934.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8567.4732c6b7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8573.cb04eda5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8577.1d54e766.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/8591.d7c16be6.js.LICENSE.txt +61 -0
- solace_agent_mesh/assets/docs/assets/js/8709.7ecd4047.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8731.49e930c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8908.f9d1b506.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9157.b4093d07.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9278.a4fd875d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/945fb41e.74d728aa.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9616.b75c2f6d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9793.c6d16376.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9eff14a2.1bf8f61c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a3a92b25.26ca071f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a7bd4aaa.2204d2f7.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba21aa0.c42a534c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/aba87c2f.d3e2dcc3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ae4415af.8e279b5d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/b7006a3a.40b10c9d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/bb2ef573.207e6990.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c2c06897.63b76e9e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cc969b05.954186d4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cd3d4052.ca6eed8c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ced92a13.fb92e7ca.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cee5d587.f5b73ca1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.ecc3d195.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f897a61a.f8c53b0f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/main.c6286d7c.js.LICENSE.txt +81 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d5133813.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +128 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +91 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +201 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +91 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +55 -0
- solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +82 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +77 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +48 -0
- solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +17 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +45 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +76 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +150 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +267 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +136 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +116 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +80 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +164 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +57 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +72 -0
- solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +102 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +99 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +90 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +107 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +152 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +103 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +170 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +200 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +54 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +69 -0
- solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +59 -0
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_README.png +0 -0
- solace_agent_mesh/assets/docs/img/Solace_AI_Framework_With_Broker.png +0 -0
- solace_agent_mesh/assets/docs/img/logo.png +0 -0
- solace_agent_mesh/assets/docs/img/sac-flows.png +0 -0
- solace_agent_mesh/assets/docs/img/sac_parts_of_a_component.png +0 -0
- solace_agent_mesh/assets/docs/img/solace-logo.png +0 -0
- solace_agent_mesh/assets/docs/lunr-index-1754075282978.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -0
- solace_agent_mesh/assets/docs/search-doc-1754075282978.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -0
- solace_agent_mesh/assets/docs/sitemap.xml +1 -0
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/__init__.py +15 -0
- solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +250 -0
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +659 -0
- solace_agent_mesh/cli/commands/add_cmd/gateway_cmd.py +322 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_agent_step.py +93 -0
- solace_agent_mesh/cli/commands/add_cmd/web_add_gateway_step.py +118 -0
- solace_agent_mesh/cli/commands/docs_cmd.py +57 -0
- solace_agent_mesh/cli/commands/eval_cmd.py +64 -0
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +404 -0
- solace_agent_mesh/cli/commands/init_cmd/broker_step.py +201 -0
- solace_agent_mesh/cli/commands/init_cmd/directory_step.py +28 -0
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +205 -0
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +407 -0
- solace_agent_mesh/cli/commands/init_cmd/project_files_step.py +38 -0
- solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +110 -0
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +183 -0
- solace_agent_mesh/cli/commands/plugin_cmd/__init__.py +18 -0
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +372 -0
- solace_agent_mesh/cli/commands/plugin_cmd/build_cmd.py +86 -0
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +139 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
- solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +175 -0
- solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +305 -0
- solace_agent_mesh/cli/commands/run_cmd.py +158 -0
- solace_agent_mesh/cli/main.py +17 -294
- solace_agent_mesh/cli/utils.py +135 -204
- solace_agent_mesh/client/webui/frontend/static/assets/authCallback-DvlO62me.js +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/client-bp6u3qVZ.js +49 -0
- solace_agent_mesh/client/webui/frontend/static/assets/favicon-BLgzUch9.ico +0 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-D11Lmy9p.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Gfk3BYn5.js +663 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +14 -0
- solace_agent_mesh/client/webui/frontend/static/index.html +15 -0
- solace_agent_mesh/common/__init__.py +1 -0
- solace_agent_mesh/common/a2a_protocol.py +564 -0
- solace_agent_mesh/common/agent_registry.py +42 -0
- solace_agent_mesh/common/client/__init__.py +4 -0
- solace_agent_mesh/common/client/card_resolver.py +21 -0
- solace_agent_mesh/common/client/client.py +85 -0
- solace_agent_mesh/common/client/client_llm.txt +133 -0
- solace_agent_mesh/common/common_llm.txt +144 -0
- solace_agent_mesh/common/constants.py +1 -14
- solace_agent_mesh/common/middleware/__init__.py +12 -0
- solace_agent_mesh/common/middleware/config_resolver.py +130 -0
- solace_agent_mesh/common/middleware/middleware_llm.txt +174 -0
- solace_agent_mesh/common/middleware/registry.py +125 -0
- solace_agent_mesh/common/server/__init__.py +4 -0
- solace_agent_mesh/common/server/server.py +122 -0
- solace_agent_mesh/common/server/server_llm.txt +169 -0
- solace_agent_mesh/common/server/task_manager.py +291 -0
- solace_agent_mesh/common/server/utils.py +28 -0
- solace_agent_mesh/common/services/__init__.py +4 -0
- solace_agent_mesh/common/services/employee_service.py +162 -0
- solace_agent_mesh/common/services/identity_service.py +129 -0
- solace_agent_mesh/common/services/providers/__init__.py +4 -0
- solace_agent_mesh/common/services/providers/local_file_identity_service.py +148 -0
- solace_agent_mesh/common/services/providers/providers_llm.txt +113 -0
- solace_agent_mesh/common/services/services_llm.txt +132 -0
- solace_agent_mesh/common/types.py +411 -0
- solace_agent_mesh/common/utils/__init__.py +7 -0
- solace_agent_mesh/common/utils/asyncio_macos_fix.py +86 -0
- solace_agent_mesh/common/utils/embeds/__init__.py +33 -0
- solace_agent_mesh/common/utils/embeds/constants.py +55 -0
- solace_agent_mesh/common/utils/embeds/converter.py +452 -0
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +124 -0
- solace_agent_mesh/common/utils/embeds/evaluators.py +394 -0
- solace_agent_mesh/common/utils/embeds/modifiers.py +816 -0
- solace_agent_mesh/common/utils/embeds/resolver.py +865 -0
- solace_agent_mesh/common/utils/embeds/types.py +14 -0
- solace_agent_mesh/common/utils/in_memory_cache.py +108 -0
- solace_agent_mesh/common/utils/initializer.py +51 -0
- solace_agent_mesh/common/utils/log_formatters.py +44 -0
- solace_agent_mesh/common/utils/mime_helpers.py +106 -0
- solace_agent_mesh/common/utils/push_notification_auth.py +134 -0
- solace_agent_mesh/common/utils/utils_llm.txt +67 -0
- solace_agent_mesh/config_portal/backend/common.py +66 -24
- solace_agent_mesh/config_portal/backend/plugin_catalog/constants.py +24 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +164 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +521 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +217 -0
- solace_agent_mesh/config_portal/backend/server.py +551 -181
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-_7yox_eh.js +48 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-B7lKcHVY.js +140 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-DX1misIU.js → entry.client-CEumGClk.js} +3 -3
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-DSo1AH_7.js +68 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-e5c3acfe.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{root-BApq5dPK.js → root-C4XmHinv.js} +2 -2
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/__init__.py +1 -0
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +88 -0
- solace_agent_mesh/core_a2a/service.py +331 -0
- solace_agent_mesh/evaluation/config_loader.py +657 -0
- solace_agent_mesh/evaluation/evaluator.py +667 -0
- solace_agent_mesh/evaluation/message_organizer.py +568 -0
- solace_agent_mesh/evaluation/report/benchmark_info.html +35 -0
- solace_agent_mesh/evaluation/report/chart_section.html +141 -0
- solace_agent_mesh/evaluation/report/detailed_breakdown.html +28 -0
- solace_agent_mesh/evaluation/report/modal.html +59 -0
- solace_agent_mesh/evaluation/report/modal_chart_functions.js +411 -0
- solace_agent_mesh/evaluation/report/modal_script.js +296 -0
- solace_agent_mesh/evaluation/report/modal_styles.css +340 -0
- solace_agent_mesh/evaluation/report/performance_metrics_styles.css +93 -0
- solace_agent_mesh/evaluation/report/templates/footer.html +2 -0
- solace_agent_mesh/evaluation/report/templates/header.html +340 -0
- solace_agent_mesh/evaluation/report_data_processor.py +972 -0
- solace_agent_mesh/evaluation/report_generator.py +613 -0
- solace_agent_mesh/evaluation/run.py +613 -0
- solace_agent_mesh/evaluation/subscriber.py +872 -0
- solace_agent_mesh/evaluation/summary_builder.py +775 -0
- solace_agent_mesh/evaluation/test_case_loader.py +714 -0
- solace_agent_mesh/gateway/base/__init__.py +1 -0
- solace_agent_mesh/gateway/base/app.py +266 -0
- solace_agent_mesh/gateway/base/base_llm.txt +119 -0
- solace_agent_mesh/gateway/base/component.py +1542 -0
- solace_agent_mesh/gateway/base/task_context.py +74 -0
- solace_agent_mesh/gateway/gateway_llm.txt +125 -0
- solace_agent_mesh/gateway/http_sse/app.py +190 -0
- solace_agent_mesh/gateway/http_sse/component.py +1602 -0
- solace_agent_mesh/gateway/http_sse/components/__init__.py +7 -0
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +65 -0
- solace_agent_mesh/gateway/http_sse/components/visualization_forwarder_component.py +108 -0
- solace_agent_mesh/gateway/http_sse/dependencies.py +316 -0
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +63 -0
- solace_agent_mesh/gateway/http_sse/main.py +442 -0
- solace_agent_mesh/gateway/http_sse/routers/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/routers/agents.py +41 -0
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +827 -0
- solace_agent_mesh/gateway/http_sse/routers/auth.py +212 -0
- solace_agent_mesh/gateway/http_sse/routers/config.py +55 -0
- solace_agent_mesh/gateway/http_sse/routers/people.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +37 -0
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +80 -0
- solace_agent_mesh/gateway/http_sse/routers/sse.py +138 -0
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +294 -0
- solace_agent_mesh/gateway/http_sse/routers/users.py +59 -0
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +1131 -0
- solace_agent_mesh/gateway/http_sse/services/__init__.py +4 -0
- solace_agent_mesh/gateway/http_sse/services/agent_service.py +69 -0
- solace_agent_mesh/gateway/http_sse/services/people_service.py +158 -0
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +179 -0
- solace_agent_mesh/gateway/http_sse/services/task_service.py +121 -0
- solace_agent_mesh/gateway/http_sse/session_manager.py +187 -0
- solace_agent_mesh/gateway/http_sse/sse_manager.py +328 -0
- solace_agent_mesh/llm.txt +228 -0
- solace_agent_mesh/llm_detail.txt +2835 -0
- solace_agent_mesh/templates/agent_template.yaml +53 -0
- solace_agent_mesh/templates/eval_backend_template.yaml +54 -0
- solace_agent_mesh/templates/gateway_app_template.py +73 -0
- solace_agent_mesh/templates/gateway_component_template.py +431 -0
- solace_agent_mesh/templates/gateway_config_template.yaml +43 -0
- solace_agent_mesh/templates/logging_config_template.ini +64 -0
- solace_agent_mesh/templates/main_orchestrator.yaml +55 -0
- solace_agent_mesh/templates/plugin_agent_config_template.yaml +122 -0
- solace_agent_mesh/templates/plugin_custom_config_template.yaml +27 -0
- solace_agent_mesh/templates/plugin_custom_template.py +10 -0
- solace_agent_mesh/templates/plugin_gateway_config_template.yaml +63 -0
- solace_agent_mesh/templates/plugin_pyproject_template.toml +33 -0
- solace_agent_mesh/templates/plugin_readme_template.md +34 -0
- solace_agent_mesh/templates/plugin_tools_template.py +224 -0
- solace_agent_mesh/templates/shared_config.yaml +66 -0
- solace_agent_mesh/templates/templates_llm.txt +147 -0
- solace_agent_mesh/templates/webui.yaml +53 -0
- solace_agent_mesh-1.0.2.dist-info/METADATA +432 -0
- solace_agent_mesh-1.0.2.dist-info/RECORD +361 -0
- solace_agent_mesh-1.0.2.dist-info/entry_points.txt +3 -0
- {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.dist-info}/licenses/LICENSE +1 -1
- solace_agent_mesh/agents/base_agent_component.py +0 -256
- solace_agent_mesh/agents/global/actions/agent_state_change.py +0 -54
- solace_agent_mesh/agents/global/actions/clear_history.py +0 -32
- solace_agent_mesh/agents/global/actions/convert_file_to_markdown.py +0 -160
- solace_agent_mesh/agents/global/actions/create_file.py +0 -70
- solace_agent_mesh/agents/global/actions/error_action.py +0 -45
- solace_agent_mesh/agents/global/actions/plantuml_diagram.py +0 -163
- solace_agent_mesh/agents/global/actions/plotly_graph.py +0 -152
- solace_agent_mesh/agents/global/actions/retrieve_file.py +0 -51
- solace_agent_mesh/agents/global/global_agent_component.py +0 -38
- solace_agent_mesh/agents/image_processing/actions/create_image.py +0 -75
- solace_agent_mesh/agents/image_processing/actions/describe_image.py +0 -115
- solace_agent_mesh/agents/image_processing/image_processing_agent_component.py +0 -23
- solace_agent_mesh/agents/slack/__init__.py +0 -1
- solace_agent_mesh/agents/slack/actions/__init__.py +0 -1
- solace_agent_mesh/agents/slack/actions/post_message.py +0 -177
- solace_agent_mesh/agents/slack/slack_agent_component.py +0 -59
- solace_agent_mesh/agents/web_request/actions/do_image_search.py +0 -84
- solace_agent_mesh/agents/web_request/actions/do_news_search.py +0 -47
- solace_agent_mesh/agents/web_request/actions/do_suggestion_search.py +0 -34
- solace_agent_mesh/agents/web_request/actions/do_web_request.py +0 -135
- solace_agent_mesh/agents/web_request/actions/download_file.py +0 -69
- solace_agent_mesh/agents/web_request/web_request_agent_component.py +0 -33
- solace_agent_mesh/assets/web-visualizer/assets/index-D0qORgkg.css +0 -1
- solace_agent_mesh/assets/web-visualizer/assets/index-DnDr1pnu.js +0 -109
- solace_agent_mesh/assets/web-visualizer/index.html +0 -14
- solace_agent_mesh/assets/web-visualizer/vite.svg +0 -1
- solace_agent_mesh/cli/commands/add/__init__.py +0 -3
- solace_agent_mesh/cli/commands/add/add.py +0 -88
- solace_agent_mesh/cli/commands/add/agent.py +0 -110
- solace_agent_mesh/cli/commands/add/copy_from_plugin.py +0 -92
- solace_agent_mesh/cli/commands/add/gateway.py +0 -374
- solace_agent_mesh/cli/commands/build.py +0 -670
- solace_agent_mesh/cli/commands/chat/__init__.py +0 -3
- solace_agent_mesh/cli/commands/chat/chat.py +0 -361
- solace_agent_mesh/cli/commands/config.py +0 -29
- solace_agent_mesh/cli/commands/init/__init__.py +0 -3
- solace_agent_mesh/cli/commands/init/ai_provider_step.py +0 -93
- solace_agent_mesh/cli/commands/init/broker_step.py +0 -99
- solace_agent_mesh/cli/commands/init/builtin_agent_step.py +0 -83
- solace_agent_mesh/cli/commands/init/check_if_already_done.py +0 -13
- solace_agent_mesh/cli/commands/init/create_config_file_step.py +0 -65
- solace_agent_mesh/cli/commands/init/create_other_project_files_step.py +0 -147
- solace_agent_mesh/cli/commands/init/file_service_step.py +0 -73
- solace_agent_mesh/cli/commands/init/init.py +0 -92
- solace_agent_mesh/cli/commands/init/project_structure_step.py +0 -16
- solace_agent_mesh/cli/commands/init/web_init_step.py +0 -32
- solace_agent_mesh/cli/commands/plugin/__init__.py +0 -3
- solace_agent_mesh/cli/commands/plugin/add.py +0 -100
- solace_agent_mesh/cli/commands/plugin/build.py +0 -268
- solace_agent_mesh/cli/commands/plugin/create.py +0 -117
- solace_agent_mesh/cli/commands/plugin/plugin.py +0 -124
- solace_agent_mesh/cli/commands/plugin/remove.py +0 -73
- solace_agent_mesh/cli/commands/run.py +0 -68
- solace_agent_mesh/cli/commands/visualizer.py +0 -138
- solace_agent_mesh/cli/config.py +0 -85
- solace_agent_mesh/common/action.py +0 -91
- solace_agent_mesh/common/action_list.py +0 -37
- solace_agent_mesh/common/action_response.py +0 -340
- solace_agent_mesh/common/mysql_database.py +0 -40
- solace_agent_mesh/common/postgres_database.py +0 -85
- solace_agent_mesh/common/prompt_templates.py +0 -28
- solace_agent_mesh/common/stimulus_utils.py +0 -152
- solace_agent_mesh/common/time.py +0 -24
- solace_agent_mesh/common/utils.py +0 -712
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-a-zJ6rLx.js +0 -46
- solace_agent_mesh/config_portal/frontend/static/client/assets/components-ZIfdTbrV.js +0 -191
- solace_agent_mesh/config_portal/frontend/static/client/assets/index-BJHAE5s4.js +0 -17
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-44c41103.js +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DX4gQ516.css +0 -1
- solace_agent_mesh/configs/agent_global.yaml +0 -74
- solace_agent_mesh/configs/agent_image_processing.yaml +0 -82
- solace_agent_mesh/configs/agent_slack.yaml +0 -64
- solace_agent_mesh/configs/agent_web_request.yaml +0 -75
- solace_agent_mesh/configs/conversation_to_file.yaml +0 -56
- solace_agent_mesh/configs/error_catcher.yaml +0 -56
- solace_agent_mesh/configs/monitor.yaml +0 -0
- solace_agent_mesh/configs/monitor_stim_and_errors_to_slack.yaml +0 -109
- solace_agent_mesh/configs/monitor_user_feedback.yaml +0 -58
- solace_agent_mesh/configs/orchestrator.yaml +0 -241
- solace_agent_mesh/configs/service_embedding.yaml +0 -81
- solace_agent_mesh/configs/service_llm.yaml +0 -265
- solace_agent_mesh/configs/visualize_websocket.yaml +0 -55
- solace_agent_mesh/gateway/components/gateway_base.py +0 -47
- solace_agent_mesh/gateway/components/gateway_input.py +0 -278
- solace_agent_mesh/gateway/components/gateway_output.py +0 -298
- solace_agent_mesh/gateway/identity/bamboohr_identity.py +0 -18
- solace_agent_mesh/gateway/identity/identity_base.py +0 -10
- solace_agent_mesh/gateway/identity/identity_provider.py +0 -60
- solace_agent_mesh/gateway/identity/no_identity.py +0 -9
- solace_agent_mesh/gateway/identity/passthru_identity.py +0 -9
- solace_agent_mesh/monitors/base_monitor_component.py +0 -26
- solace_agent_mesh/monitors/feedback/user_feedback_monitor.py +0 -75
- solace_agent_mesh/monitors/stim_and_errors/stim_and_error_monitor.py +0 -560
- solace_agent_mesh/orchestrator/__init__.py +0 -0
- solace_agent_mesh/orchestrator/action_manager.py +0 -237
- solace_agent_mesh/orchestrator/components/__init__.py +0 -0
- solace_agent_mesh/orchestrator/components/orchestrator_action_manager_timeout_component.py +0 -58
- solace_agent_mesh/orchestrator/components/orchestrator_action_response_component.py +0 -179
- solace_agent_mesh/orchestrator/components/orchestrator_register_component.py +0 -107
- solace_agent_mesh/orchestrator/components/orchestrator_stimulus_processor_component.py +0 -527
- solace_agent_mesh/orchestrator/components/orchestrator_streaming_output_component.py +0 -260
- solace_agent_mesh/orchestrator/orchestrator_main.py +0 -172
- solace_agent_mesh/orchestrator/orchestrator_prompt.py +0 -539
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/authorization/providers/base_authorization_provider.py +0 -56
- solace_agent_mesh/services/bamboo_hr_service/__init__.py +0 -3
- solace_agent_mesh/services/bamboo_hr_service/bamboo_hr.py +0 -182
- solace_agent_mesh/services/common/__init__.py +0 -4
- solace_agent_mesh/services/common/auto_expiry.py +0 -45
- solace_agent_mesh/services/common/singleton.py +0 -18
- solace_agent_mesh/services/file_service/__init__.py +0 -14
- solace_agent_mesh/services/file_service/file_manager/__init__.py +0 -0
- solace_agent_mesh/services/file_service/file_manager/bucket_file_manager.py +0 -149
- solace_agent_mesh/services/file_service/file_manager/file_manager_base.py +0 -162
- solace_agent_mesh/services/file_service/file_manager/memory_file_manager.py +0 -64
- solace_agent_mesh/services/file_service/file_manager/volume_file_manager.py +0 -106
- solace_agent_mesh/services/file_service/file_service.py +0 -437
- solace_agent_mesh/services/file_service/file_service_constants.py +0 -54
- solace_agent_mesh/services/file_service/file_transformations.py +0 -141
- solace_agent_mesh/services/file_service/file_utils.py +0 -324
- solace_agent_mesh/services/file_service/transformers/__init__.py +0 -5
- solace_agent_mesh/services/history_service/__init__.py +0 -3
- solace_agent_mesh/services/history_service/history_providers/__init__.py +0 -0
- solace_agent_mesh/services/history_service/history_providers/base_history_provider.py +0 -54
- solace_agent_mesh/services/history_service/history_providers/file_history_provider.py +0 -74
- solace_agent_mesh/services/history_service/history_providers/index.py +0 -40
- solace_agent_mesh/services/history_service/history_providers/memory_history_provider.py +0 -33
- solace_agent_mesh/services/history_service/history_providers/mongodb_history_provider.py +0 -66
- solace_agent_mesh/services/history_service/history_providers/redis_history_provider.py +0 -66
- solace_agent_mesh/services/history_service/history_providers/sql_history_provider.py +0 -93
- solace_agent_mesh/services/history_service/history_service.py +0 -413
- solace_agent_mesh/services/history_service/long_term_memory/__init__.py +0 -0
- solace_agent_mesh/services/history_service/long_term_memory/long_term_memory.py +0 -399
- solace_agent_mesh/services/llm_service/components/llm_request_component.py +0 -340
- solace_agent_mesh/services/llm_service/components/llm_service_component_base.py +0 -152
- solace_agent_mesh/services/middleware_service/__init__.py +0 -0
- solace_agent_mesh/services/middleware_service/middleware_service.py +0 -20
- solace_agent_mesh/templates/action.py +0 -38
- solace_agent_mesh/templates/agent.py +0 -29
- solace_agent_mesh/templates/agent.yaml +0 -70
- solace_agent_mesh/templates/gateway-config-template.yaml +0 -6
- solace_agent_mesh/templates/gateway-default-config.yaml +0 -28
- solace_agent_mesh/templates/gateway-flows.yaml +0 -78
- solace_agent_mesh/templates/gateway-header.yaml +0 -16
- solace_agent_mesh/templates/gateway_base.py +0 -15
- solace_agent_mesh/templates/gateway_input.py +0 -98
- solace_agent_mesh/templates/gateway_output.py +0 -71
- solace_agent_mesh/templates/plugin-gateway-default-config.yaml +0 -29
- solace_agent_mesh/templates/plugin-pyproject.toml +0 -30
- solace_agent_mesh/templates/rest-api-default-config.yaml +0 -31
- solace_agent_mesh/templates/rest-api-flows.yaml +0 -81
- solace_agent_mesh/templates/slack-default-config.yaml +0 -16
- solace_agent_mesh/templates/slack-flows.yaml +0 -81
- solace_agent_mesh/templates/solace-agent-mesh-default.yaml +0 -86
- solace_agent_mesh/templates/solace-agent-mesh-plugin-default.yaml +0 -8
- solace_agent_mesh/templates/web-default-config.yaml +0 -10
- solace_agent_mesh/templates/web-flows.yaml +0 -76
- solace_agent_mesh/tools/__init__.py +0 -0
- solace_agent_mesh/tools/components/__init__.py +0 -0
- solace_agent_mesh/tools/components/conversation_formatter.py +0 -111
- solace_agent_mesh/tools/components/file_resolver_component.py +0 -58
- solace_agent_mesh/tools/config/runtime_config.py +0 -26
- solace_agent_mesh-0.2.4.dist-info/METADATA +0 -176
- solace_agent_mesh-0.2.4.dist-info/RECORD +0 -193
- solace_agent_mesh-0.2.4.dist-info/entry_points.txt +0 -3
- /solace_agent_mesh/{agents → agent}/__init__.py +0 -0
- /solace_agent_mesh/{agents/global → agent/adk}/__init__.py +0 -0
- /solace_agent_mesh/{agents/global/actions → agent/protocol}/__init__.py +0 -0
- /solace_agent_mesh/{agents/image_processing → agent/sac}/__init__.py +0 -0
- /solace_agent_mesh/{agents/image_processing/actions → agent/utils}/__init__.py +0 -0
- /solace_agent_mesh/{agents/web_request → config_portal/backend/plugin_catalog}/__init__.py +0 -0
- /solace_agent_mesh/{agents/web_request/actions → evaluation}/__init__.py +0 -0
- /solace_agent_mesh/gateway/{components → http_sse}/__init__.py +0 -0
- {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Refactored message subscriber with improved structure and readability.
|
|
3
|
+
This module handles Solace message subscription and processing for evaluation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from typing import Dict, List, Optional, Any, Set, Callable
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
import logging
|
|
14
|
+
from dotenv import load_dotenv
|
|
15
|
+
|
|
16
|
+
from solace.messaging.messaging_service import MessagingService
|
|
17
|
+
from solace.messaging.resources.topic_subscription import TopicSubscription
|
|
18
|
+
from solace.messaging.config.solace_properties import (
|
|
19
|
+
transport_layer_properties,
|
|
20
|
+
service_properties,
|
|
21
|
+
authentication_properties,
|
|
22
|
+
transport_layer_security_properties,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Set up logging
|
|
26
|
+
logging.basicConfig(level=logging.INFO)
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Load environment variables
|
|
30
|
+
load_dotenv()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ConnectionState(Enum):
|
|
34
|
+
"""Represents the connection state of the subscriber."""
|
|
35
|
+
|
|
36
|
+
DISCONNECTED = "disconnected"
|
|
37
|
+
CONNECTING = "connecting"
|
|
38
|
+
CONNECTED = "connected"
|
|
39
|
+
DISCONNECTING = "disconnecting"
|
|
40
|
+
ERROR = "error"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SubscriberError(Exception):
|
|
44
|
+
"""Base exception for subscriber-related errors."""
|
|
45
|
+
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BrokerConnectionError(SubscriberError):
|
|
50
|
+
"""Raised when broker connection fails."""
|
|
51
|
+
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MessageProcessingError(SubscriberError):
|
|
56
|
+
"""Raised when message processing fails."""
|
|
57
|
+
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ConfigurationError(SubscriberError):
|
|
62
|
+
"""Raised when configuration is invalid."""
|
|
63
|
+
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class BrokerConfig:
|
|
69
|
+
"""Broker connection configuration with validation."""
|
|
70
|
+
|
|
71
|
+
host: str
|
|
72
|
+
vpn_name: str
|
|
73
|
+
username: str
|
|
74
|
+
password: str
|
|
75
|
+
cert_validated: bool = False
|
|
76
|
+
connection_timeout: int = 30
|
|
77
|
+
reconnect_attempts: int = 3
|
|
78
|
+
reconnect_delay: float = 1.0
|
|
79
|
+
|
|
80
|
+
def __post_init__(self):
|
|
81
|
+
"""Validate broker configuration after initialization."""
|
|
82
|
+
if not self.host or not self.host.strip():
|
|
83
|
+
raise ConfigurationError("Broker host cannot be empty")
|
|
84
|
+
|
|
85
|
+
if not self.vpn_name or not self.vpn_name.strip():
|
|
86
|
+
raise ConfigurationError("VPN name cannot be empty")
|
|
87
|
+
|
|
88
|
+
if not self.username or not self.username.strip():
|
|
89
|
+
raise ConfigurationError("Username cannot be empty")
|
|
90
|
+
|
|
91
|
+
if not self.password or not self.password.strip():
|
|
92
|
+
raise ConfigurationError("Password cannot be empty")
|
|
93
|
+
|
|
94
|
+
if self.connection_timeout <= 0:
|
|
95
|
+
raise ConfigurationError("Connection timeout must be positive")
|
|
96
|
+
|
|
97
|
+
if self.reconnect_attempts < 0:
|
|
98
|
+
raise ConfigurationError("Reconnect attempts cannot be negative")
|
|
99
|
+
|
|
100
|
+
def to_solace_properties(self) -> Dict[str, Any]:
|
|
101
|
+
"""Convert to Solace messaging properties."""
|
|
102
|
+
return {
|
|
103
|
+
transport_layer_properties.HOST: self.host,
|
|
104
|
+
service_properties.VPN_NAME: self.vpn_name,
|
|
105
|
+
authentication_properties.SCHEME_BASIC_USER_NAME: self.username,
|
|
106
|
+
authentication_properties.SCHEME_BASIC_PASSWORD: self.password,
|
|
107
|
+
transport_layer_security_properties.CERT_VALIDATED: self.cert_validated,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class SubscriptionConfig:
|
|
113
|
+
"""Subscription configuration and topic filters."""
|
|
114
|
+
|
|
115
|
+
namespace: str
|
|
116
|
+
allowed_topic_infixes: List[str] = field(
|
|
117
|
+
default_factory=lambda: [
|
|
118
|
+
"/agent/request/",
|
|
119
|
+
"/gateway/status/",
|
|
120
|
+
"/gateway/response/",
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
message_timeout: int = 1000
|
|
124
|
+
filter_non_final_status: bool = True
|
|
125
|
+
remove_config_keys: bool = True
|
|
126
|
+
|
|
127
|
+
def __post_init__(self):
|
|
128
|
+
"""Validate subscription configuration."""
|
|
129
|
+
if not self.namespace or not self.namespace.strip():
|
|
130
|
+
raise ConfigurationError("Namespace cannot be empty")
|
|
131
|
+
|
|
132
|
+
if not self.allowed_topic_infixes:
|
|
133
|
+
raise ConfigurationError("At least one topic infix must be allowed")
|
|
134
|
+
|
|
135
|
+
if self.message_timeout <= 0:
|
|
136
|
+
raise ConfigurationError("Message timeout must be positive")
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def topic_pattern(self) -> str:
|
|
140
|
+
"""Get the topic subscription pattern."""
|
|
141
|
+
return f"{self.namespace}/a2a/v1/>"
|
|
142
|
+
|
|
143
|
+
def is_topic_allowed(self, topic: str) -> bool:
|
|
144
|
+
"""Check if a topic is allowed based on configured infixes."""
|
|
145
|
+
return any(infix in topic for infix in self.allowed_topic_infixes)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class ProcessedMessage:
|
|
150
|
+
"""Structured representation of a processed message."""
|
|
151
|
+
|
|
152
|
+
topic: str
|
|
153
|
+
payload: Any
|
|
154
|
+
timestamp: float = field(default_factory=time.time)
|
|
155
|
+
message_type: Optional[str] = None
|
|
156
|
+
|
|
157
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
158
|
+
"""Convert to dictionary for JSON serialization."""
|
|
159
|
+
return {
|
|
160
|
+
"topic": self.topic,
|
|
161
|
+
"payload": self.payload,
|
|
162
|
+
"timestamp": self.timestamp,
|
|
163
|
+
"message_type": self.message_type,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class TaskCompletionEvent:
|
|
169
|
+
"""Represents a task completion event."""
|
|
170
|
+
|
|
171
|
+
task_id: str
|
|
172
|
+
topic: str
|
|
173
|
+
timestamp: float = field(default_factory=time.time)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class MessageSanitizer:
|
|
177
|
+
"""Handles message sanitization and cleaning."""
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def remove_key_recursive(obj: Any, key_to_remove: str) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Recursively remove a key from nested dictionaries and lists.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
obj: The object to process (dict, list, or other)
|
|
186
|
+
key_to_remove: The key to remove from dictionaries
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
if isinstance(obj, dict):
|
|
190
|
+
# Create a list of keys to avoid modifying dict during iteration
|
|
191
|
+
keys_to_process = list(obj.keys())
|
|
192
|
+
for key in keys_to_process:
|
|
193
|
+
if key == key_to_remove:
|
|
194
|
+
del obj[key]
|
|
195
|
+
else:
|
|
196
|
+
MessageSanitizer.remove_key_recursive(obj[key], key_to_remove)
|
|
197
|
+
elif isinstance(obj, list):
|
|
198
|
+
for item in obj:
|
|
199
|
+
MessageSanitizer.remove_key_recursive(item, key_to_remove)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
logger.warning(f"Error during key removal: {e}")
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def sanitize_message(payload: Any, remove_config: bool = True) -> Any:
|
|
205
|
+
"""
|
|
206
|
+
Sanitize message payload by removing unwanted keys.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
payload: The payload to sanitize
|
|
210
|
+
remove_config: Whether to remove 'config' keys
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Sanitized payload
|
|
214
|
+
"""
|
|
215
|
+
if remove_config and isinstance(payload, (dict, list)):
|
|
216
|
+
# Work on a copy to avoid modifying the original
|
|
217
|
+
import copy
|
|
218
|
+
|
|
219
|
+
sanitized = copy.deepcopy(payload)
|
|
220
|
+
MessageSanitizer.remove_key_recursive(sanitized, "config")
|
|
221
|
+
return sanitized
|
|
222
|
+
return payload
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class MessageProcessor:
|
|
226
|
+
"""Processes and filters incoming messages."""
|
|
227
|
+
|
|
228
|
+
def __init__(self, config: SubscriptionConfig):
|
|
229
|
+
self.config = config
|
|
230
|
+
self.sanitizer = MessageSanitizer()
|
|
231
|
+
self.processed_count = 0
|
|
232
|
+
self.error_count = 0
|
|
233
|
+
|
|
234
|
+
def process_message(self, inbound_message) -> Optional[ProcessedMessage]:
|
|
235
|
+
"""
|
|
236
|
+
Process an inbound message and return a ProcessedMessage if valid.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
inbound_message: The inbound Solace message
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
ProcessedMessage if the message should be kept, None otherwise
|
|
243
|
+
"""
|
|
244
|
+
try:
|
|
245
|
+
topic = inbound_message.get_destination_name()
|
|
246
|
+
|
|
247
|
+
# Check if topic is allowed
|
|
248
|
+
if not self.config.is_topic_allowed(topic):
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
# Extract and parse payload
|
|
252
|
+
payload = self._extract_payload(inbound_message)
|
|
253
|
+
if payload is None:
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
# Filter status messages if configured
|
|
257
|
+
if self._should_filter_status_message(topic, payload):
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
# Sanitize payload
|
|
261
|
+
if self.config.remove_config_keys:
|
|
262
|
+
payload = self.sanitizer.sanitize_message(payload)
|
|
263
|
+
|
|
264
|
+
# Determine message type
|
|
265
|
+
message_type = self._determine_message_type(topic)
|
|
266
|
+
|
|
267
|
+
self.processed_count += 1
|
|
268
|
+
|
|
269
|
+
return ProcessedMessage(
|
|
270
|
+
topic=topic, payload=payload, message_type=message_type
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
except Exception as e:
|
|
274
|
+
self.error_count += 1
|
|
275
|
+
logger.warning(f"Error processing message: {e}")
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
def _extract_payload(self, inbound_message) -> Optional[Any]:
|
|
279
|
+
"""Extract and parse payload from inbound message."""
|
|
280
|
+
try:
|
|
281
|
+
payload_bytes = inbound_message.get_payload_as_bytes()
|
|
282
|
+
if not payload_bytes:
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
payload_str = payload_bytes.decode("utf-8", errors="ignore")
|
|
286
|
+
|
|
287
|
+
# Try to parse as JSON
|
|
288
|
+
try:
|
|
289
|
+
return json.loads(payload_str)
|
|
290
|
+
except json.JSONDecodeError:
|
|
291
|
+
# Return as string if not valid JSON
|
|
292
|
+
return payload_str
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.warning(f"Error extracting payload: {e}")
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
def _should_filter_status_message(self, topic: str, payload: Any) -> bool:
|
|
299
|
+
"""Check if a status message should be filtered out."""
|
|
300
|
+
if not self.config.filter_non_final_status:
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
if "/gateway/status/" not in topic:
|
|
304
|
+
return False
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
if isinstance(payload, dict):
|
|
308
|
+
result = payload.get("result", {})
|
|
309
|
+
if isinstance(result, dict):
|
|
310
|
+
# Filter out non-final status messages
|
|
311
|
+
return not result.get("final", True)
|
|
312
|
+
except Exception:
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
def _determine_message_type(self, topic: str) -> str:
|
|
318
|
+
"""Determine the type of message based on topic."""
|
|
319
|
+
if "/agent/request/" in topic:
|
|
320
|
+
return "agent_request"
|
|
321
|
+
elif "/gateway/status/" in topic:
|
|
322
|
+
return "gateway_status"
|
|
323
|
+
elif "/gateway/response/" in topic:
|
|
324
|
+
return "gateway_response"
|
|
325
|
+
else:
|
|
326
|
+
return "unknown"
|
|
327
|
+
|
|
328
|
+
def get_stats(self) -> Dict[str, int]:
|
|
329
|
+
"""Get processing statistics."""
|
|
330
|
+
return {
|
|
331
|
+
"processed_count": self.processed_count,
|
|
332
|
+
"error_count": self.error_count,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class TaskTracker:
|
|
337
|
+
"""Tracks task completion and manages active tasks."""
|
|
338
|
+
|
|
339
|
+
def __init__(
|
|
340
|
+
self, active_tasks: Set[str], wave_complete_event: Optional[threading.Event]
|
|
341
|
+
):
|
|
342
|
+
self.active_tasks = active_tasks
|
|
343
|
+
self.wave_complete_event = wave_complete_event
|
|
344
|
+
self.completed_tasks: List[TaskCompletionEvent] = []
|
|
345
|
+
self._lock = threading.Lock()
|
|
346
|
+
|
|
347
|
+
def handle_task_completion(self, topic: str) -> Optional[TaskCompletionEvent]:
|
|
348
|
+
"""
|
|
349
|
+
Handle task completion based on topic.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
topic: The message topic
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
TaskCompletionEvent if a task was completed, None otherwise
|
|
356
|
+
"""
|
|
357
|
+
if "response" not in topic:
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
try:
|
|
361
|
+
task_id = self._extract_task_id(topic)
|
|
362
|
+
if not task_id:
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
with self._lock:
|
|
366
|
+
if task_id in self.active_tasks:
|
|
367
|
+
logger.info(f"Task {task_id} completed")
|
|
368
|
+
self.active_tasks.remove(task_id)
|
|
369
|
+
|
|
370
|
+
completion_event = TaskCompletionEvent(task_id=task_id, topic=topic)
|
|
371
|
+
self.completed_tasks.append(completion_event)
|
|
372
|
+
|
|
373
|
+
# Check if all tasks are complete
|
|
374
|
+
if not self.active_tasks and self.wave_complete_event:
|
|
375
|
+
logger.info("All tasks completed, setting wave complete event")
|
|
376
|
+
self.wave_complete_event.set()
|
|
377
|
+
|
|
378
|
+
return completion_event
|
|
379
|
+
|
|
380
|
+
except Exception as e:
|
|
381
|
+
logger.error(f"Error handling task completion: {e}")
|
|
382
|
+
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
def _extract_task_id(self, topic: str) -> Optional[str]:
|
|
386
|
+
"""Extract task ID from topic."""
|
|
387
|
+
try:
|
|
388
|
+
return topic.split("/")[-1]
|
|
389
|
+
except (IndexError, AttributeError):
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
def get_active_task_count(self) -> int:
|
|
393
|
+
"""Get the number of active tasks."""
|
|
394
|
+
with self._lock:
|
|
395
|
+
return len(self.active_tasks)
|
|
396
|
+
|
|
397
|
+
def get_completed_task_count(self) -> int:
|
|
398
|
+
"""Get the number of completed tasks."""
|
|
399
|
+
with self._lock:
|
|
400
|
+
return len(self.completed_tasks)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class MessageStorage:
|
|
404
|
+
"""Handles message storage and file operations."""
|
|
405
|
+
|
|
406
|
+
def __init__(self, results_path: str):
|
|
407
|
+
self.results_path = results_path
|
|
408
|
+
self.messages: List[ProcessedMessage] = []
|
|
409
|
+
self._lock = threading.Lock()
|
|
410
|
+
|
|
411
|
+
def add_message(self, message: ProcessedMessage) -> None:
|
|
412
|
+
"""Add a message to storage."""
|
|
413
|
+
with self._lock:
|
|
414
|
+
self.messages.append(message)
|
|
415
|
+
|
|
416
|
+
def get_message_count(self) -> int:
|
|
417
|
+
"""Get the number of stored messages."""
|
|
418
|
+
with self._lock:
|
|
419
|
+
return len(self.messages)
|
|
420
|
+
|
|
421
|
+
def save_messages(self, filename: str = "full_messages.json") -> str:
|
|
422
|
+
"""
|
|
423
|
+
Save all messages to a JSON file.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
filename: The filename to save to
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
The full path to the saved file
|
|
430
|
+
"""
|
|
431
|
+
output_file = os.path.join(self.results_path, filename)
|
|
432
|
+
|
|
433
|
+
try:
|
|
434
|
+
# Ensure directory exists
|
|
435
|
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
|
436
|
+
|
|
437
|
+
with self._lock:
|
|
438
|
+
# Convert messages to dictionaries for JSON serialization
|
|
439
|
+
message_dicts = [msg.to_dict() for msg in self.messages]
|
|
440
|
+
|
|
441
|
+
with open(output_file, "w") as f:
|
|
442
|
+
json.dump(message_dicts, f, indent=4)
|
|
443
|
+
|
|
444
|
+
logger.info(f"Saved {len(message_dicts)} messages to {output_file}")
|
|
445
|
+
return output_file
|
|
446
|
+
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.error(f"Error saving messages: {e}")
|
|
449
|
+
raise MessageProcessingError(f"Failed to save messages: {e}")
|
|
450
|
+
|
|
451
|
+
def clear_messages(self) -> None:
|
|
452
|
+
"""Clear all stored messages."""
|
|
453
|
+
with self._lock:
|
|
454
|
+
self.messages.clear()
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class BrokerConnectionService:
|
|
458
|
+
"""Handles Solace broker connection and lifecycle."""
|
|
459
|
+
|
|
460
|
+
def __init__(self, config: BrokerConfig):
|
|
461
|
+
self.config = config
|
|
462
|
+
self.messaging_service: Optional[MessagingService] = None
|
|
463
|
+
self.connection_state = ConnectionState.DISCONNECTED
|
|
464
|
+
self._connection_lock = threading.Lock()
|
|
465
|
+
|
|
466
|
+
def connect(self) -> None:
|
|
467
|
+
"""Connect to the Solace broker."""
|
|
468
|
+
with self._connection_lock:
|
|
469
|
+
if self.connection_state == ConnectionState.CONNECTED:
|
|
470
|
+
logger.warning("Already connected to broker")
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
self.connection_state = ConnectionState.CONNECTING
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
logger.info("Connecting to Solace PubSub+ Broker...")
|
|
477
|
+
|
|
478
|
+
broker_props = self.config.to_solace_properties()
|
|
479
|
+
self.messaging_service = (
|
|
480
|
+
MessagingService.builder().from_properties(broker_props).build()
|
|
481
|
+
)
|
|
482
|
+
self.messaging_service.connect()
|
|
483
|
+
|
|
484
|
+
self.connection_state = ConnectionState.CONNECTED
|
|
485
|
+
logger.info("Successfully connected to broker")
|
|
486
|
+
|
|
487
|
+
except Exception as e:
|
|
488
|
+
self.connection_state = ConnectionState.ERROR
|
|
489
|
+
logger.error(f"Failed to connect to broker: {e}")
|
|
490
|
+
raise BrokerConnectionError(f"Connection failed: {e}")
|
|
491
|
+
|
|
492
|
+
def disconnect(self) -> None:
|
|
493
|
+
"""Disconnect from the Solace broker."""
|
|
494
|
+
with self._connection_lock:
|
|
495
|
+
if self.connection_state == ConnectionState.DISCONNECTED:
|
|
496
|
+
logger.warning("Already disconnected from broker")
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
self.connection_state = ConnectionState.DISCONNECTING
|
|
500
|
+
|
|
501
|
+
try:
|
|
502
|
+
if self.messaging_service:
|
|
503
|
+
logger.info("Disconnecting from broker...")
|
|
504
|
+
self.messaging_service.disconnect()
|
|
505
|
+
self.messaging_service = None
|
|
506
|
+
|
|
507
|
+
self.connection_state = ConnectionState.DISCONNECTED
|
|
508
|
+
logger.info("Successfully disconnected from broker")
|
|
509
|
+
|
|
510
|
+
except Exception as e:
|
|
511
|
+
self.connection_state = ConnectionState.ERROR
|
|
512
|
+
logger.error(f"Error during disconnect: {e}")
|
|
513
|
+
raise BrokerConnectionError(f"Disconnect failed: {e}")
|
|
514
|
+
|
|
515
|
+
def get_messaging_service(self) -> Optional[MessagingService]:
|
|
516
|
+
"""Get the messaging service instance."""
|
|
517
|
+
return self.messaging_service
|
|
518
|
+
|
|
519
|
+
def is_connected(self) -> bool:
|
|
520
|
+
"""Check if currently connected to broker."""
|
|
521
|
+
return self.connection_state == ConnectionState.CONNECTED
|
|
522
|
+
|
|
523
|
+
def get_connection_state(self) -> ConnectionState:
|
|
524
|
+
"""Get the current connection state."""
|
|
525
|
+
return self.connection_state
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
class SubscriptionManager:
|
|
529
|
+
"""Manages topic subscriptions and message receiving."""
|
|
530
|
+
|
|
531
|
+
def __init__(
|
|
532
|
+
self, connection_service: BrokerConnectionService, config: SubscriptionConfig
|
|
533
|
+
):
|
|
534
|
+
self.connection_service = connection_service
|
|
535
|
+
self.config = config
|
|
536
|
+
self.message_receiver = None
|
|
537
|
+
self.subscription_active = False
|
|
538
|
+
self._receiver_lock = threading.Lock()
|
|
539
|
+
|
|
540
|
+
def start_subscription(
|
|
541
|
+
self, subscription_ready_event: Optional[threading.Event] = None
|
|
542
|
+
) -> None:
|
|
543
|
+
"""Start message subscription."""
|
|
544
|
+
with self._receiver_lock:
|
|
545
|
+
if self.subscription_active:
|
|
546
|
+
logger.warning("Subscription already active")
|
|
547
|
+
return
|
|
548
|
+
|
|
549
|
+
if not self.connection_service.is_connected():
|
|
550
|
+
raise BrokerConnectionError(
|
|
551
|
+
"Must be connected to broker before starting subscription"
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
try:
|
|
555
|
+
messaging_service = self.connection_service.get_messaging_service()
|
|
556
|
+
if not messaging_service:
|
|
557
|
+
raise BrokerConnectionError("No messaging service available")
|
|
558
|
+
|
|
559
|
+
# Create and start message receiver
|
|
560
|
+
self.message_receiver = (
|
|
561
|
+
messaging_service.create_direct_message_receiver_builder().build()
|
|
562
|
+
)
|
|
563
|
+
self.message_receiver.start()
|
|
564
|
+
|
|
565
|
+
# Add subscription
|
|
566
|
+
subscription = TopicSubscription.of(self.config.topic_pattern)
|
|
567
|
+
self.message_receiver.add_subscription(subscription)
|
|
568
|
+
|
|
569
|
+
self.subscription_active = True
|
|
570
|
+
logger.info(f"Started subscription to: {self.config.topic_pattern}")
|
|
571
|
+
|
|
572
|
+
# Signal that subscription is ready
|
|
573
|
+
if subscription_ready_event:
|
|
574
|
+
subscription_ready_event.set()
|
|
575
|
+
|
|
576
|
+
except Exception as e:
|
|
577
|
+
logger.error(f"Failed to start subscription: {e}")
|
|
578
|
+
raise BrokerConnectionError(f"Subscription failed: {e}")
|
|
579
|
+
|
|
580
|
+
def receive_message(self, timeout: Optional[int] = None):
|
|
581
|
+
"""
|
|
582
|
+
Receive a message from the subscription.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
timeout: Timeout in milliseconds, uses config default if None
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
Received message or None if timeout
|
|
589
|
+
"""
|
|
590
|
+
if not self.subscription_active or not self.message_receiver:
|
|
591
|
+
return None
|
|
592
|
+
|
|
593
|
+
timeout_ms = timeout or self.config.message_timeout
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
return self.message_receiver.receive_message(timeout=timeout_ms)
|
|
597
|
+
except Exception as e:
|
|
598
|
+
logger.warning(f"Error receiving message: {e}")
|
|
599
|
+
return None
|
|
600
|
+
|
|
601
|
+
def stop_subscription(self) -> None:
|
|
602
|
+
"""Stop message subscription."""
|
|
603
|
+
with self._receiver_lock:
|
|
604
|
+
if not self.subscription_active:
|
|
605
|
+
return
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
if self.message_receiver:
|
|
609
|
+
logger.info("Stopping message receiver...")
|
|
610
|
+
self.message_receiver.terminate()
|
|
611
|
+
self.message_receiver = None
|
|
612
|
+
|
|
613
|
+
self.subscription_active = False
|
|
614
|
+
logger.info("Subscription stopped")
|
|
615
|
+
|
|
616
|
+
except Exception as e:
|
|
617
|
+
logger.error(f"Error stopping subscription: {e}")
|
|
618
|
+
|
|
619
|
+
def is_active(self) -> bool:
|
|
620
|
+
"""Check if subscription is active."""
|
|
621
|
+
return self.subscription_active
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class MessageSubscriber(threading.Thread):
|
|
625
|
+
"""
|
|
626
|
+
Main message subscriber class that orchestrates all components.
|
|
627
|
+
|
|
628
|
+
This is the refactored version of the original Subscriber class,
|
|
629
|
+
maintaining the same interface while providing better structure.
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
def __init__(
|
|
633
|
+
self,
|
|
634
|
+
namespace: str,
|
|
635
|
+
active_tasks: Set[str],
|
|
636
|
+
wave_complete_event: Optional[threading.Event],
|
|
637
|
+
subscription_ready_event: Optional[threading.Event],
|
|
638
|
+
results_path: str,
|
|
639
|
+
):
|
|
640
|
+
"""
|
|
641
|
+
Initialize the message subscriber.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
namespace: The namespace for topic subscription
|
|
645
|
+
active_tasks: Set of active task IDs to track
|
|
646
|
+
wave_complete_event: Event to set when all tasks complete
|
|
647
|
+
subscription_ready_event: Event to set when subscription is ready
|
|
648
|
+
results_path: Path to save results
|
|
649
|
+
"""
|
|
650
|
+
super().__init__(name="MessageSubscriber")
|
|
651
|
+
|
|
652
|
+
# Initialize configuration
|
|
653
|
+
self.broker_config = self._create_broker_config()
|
|
654
|
+
self.subscription_config = SubscriptionConfig(namespace=namespace)
|
|
655
|
+
|
|
656
|
+
# Initialize services
|
|
657
|
+
self.connection_service = BrokerConnectionService(self.broker_config)
|
|
658
|
+
self.subscription_manager = SubscriptionManager(
|
|
659
|
+
self.connection_service, self.subscription_config
|
|
660
|
+
)
|
|
661
|
+
self.message_processor = MessageProcessor(self.subscription_config)
|
|
662
|
+
self.task_tracker = TaskTracker(active_tasks, wave_complete_event)
|
|
663
|
+
self.message_storage = MessageStorage(results_path)
|
|
664
|
+
|
|
665
|
+
# Thread control
|
|
666
|
+
self._running = False
|
|
667
|
+
self._subscription_ready_event = subscription_ready_event
|
|
668
|
+
|
|
669
|
+
# Statistics
|
|
670
|
+
self.start_time = time.time()
|
|
671
|
+
self.messages_received = 0
|
|
672
|
+
self.messages_processed = 0
|
|
673
|
+
|
|
674
|
+
def _create_broker_config(self) -> BrokerConfig:
|
|
675
|
+
"""Create broker configuration from environment variables."""
|
|
676
|
+
try:
|
|
677
|
+
return BrokerConfig(
|
|
678
|
+
host=os.environ.get("SOLACE_BROKER_URL", ""),
|
|
679
|
+
vpn_name=os.environ.get("SOLACE_BROKER_VPN", ""),
|
|
680
|
+
username=os.environ.get("SOLACE_BROKER_USERNAME", ""),
|
|
681
|
+
password=os.environ.get("SOLACE_BROKER_PASSWORD", ""),
|
|
682
|
+
)
|
|
683
|
+
except ConfigurationError as e:
|
|
684
|
+
logger.error(f"Invalid broker configuration: {e}")
|
|
685
|
+
raise
|
|
686
|
+
|
|
687
|
+
def run(self) -> None:
|
|
688
|
+
"""Main thread execution method."""
|
|
689
|
+
try:
|
|
690
|
+
self._running = True
|
|
691
|
+
logger.info("Starting message subscriber...")
|
|
692
|
+
|
|
693
|
+
# Connect to broker
|
|
694
|
+
self.connection_service.connect()
|
|
695
|
+
|
|
696
|
+
# Start subscription
|
|
697
|
+
self.subscription_manager.start_subscription(self._subscription_ready_event)
|
|
698
|
+
|
|
699
|
+
# Main message processing loop
|
|
700
|
+
self._message_processing_loop()
|
|
701
|
+
|
|
702
|
+
except Exception as e:
|
|
703
|
+
logger.error(f"Error in subscriber thread: {e}")
|
|
704
|
+
finally:
|
|
705
|
+
self._cleanup()
|
|
706
|
+
|
|
707
|
+
def _message_processing_loop(self) -> None:
|
|
708
|
+
"""Main message processing loop."""
|
|
709
|
+
logger.info("Starting message processing loop...")
|
|
710
|
+
|
|
711
|
+
while self._running:
|
|
712
|
+
try:
|
|
713
|
+
# Receive message with timeout
|
|
714
|
+
inbound_message = self.subscription_manager.receive_message()
|
|
715
|
+
|
|
716
|
+
if inbound_message:
|
|
717
|
+
self.messages_received += 1
|
|
718
|
+
self._handle_inbound_message(inbound_message)
|
|
719
|
+
|
|
720
|
+
except Exception as e:
|
|
721
|
+
if self._running:
|
|
722
|
+
logger.error(f"Error in message processing loop: {e}")
|
|
723
|
+
# Continue processing other messages
|
|
724
|
+
continue
|
|
725
|
+
|
|
726
|
+
def _handle_inbound_message(self, inbound_message) -> None:
|
|
727
|
+
"""Handle a single inbound message."""
|
|
728
|
+
try:
|
|
729
|
+
# Process the message
|
|
730
|
+
processed_message = self.message_processor.process_message(inbound_message)
|
|
731
|
+
|
|
732
|
+
if processed_message:
|
|
733
|
+
self.messages_processed += 1
|
|
734
|
+
|
|
735
|
+
# Store the message
|
|
736
|
+
self.message_storage.add_message(processed_message)
|
|
737
|
+
|
|
738
|
+
# Handle task completion if applicable
|
|
739
|
+
self.task_tracker.handle_task_completion(processed_message.topic)
|
|
740
|
+
|
|
741
|
+
except Exception as e:
|
|
742
|
+
logger.warning(f"Error handling message: {e}")
|
|
743
|
+
|
|
744
|
+
def stop(self) -> None:
|
|
745
|
+
"""Stop the subscriber and clean up resources."""
|
|
746
|
+
logger.info("Stopping message subscriber...")
|
|
747
|
+
self._running = False
|
|
748
|
+
|
|
749
|
+
def _cleanup(self) -> None:
|
|
750
|
+
"""Clean up all resources."""
|
|
751
|
+
try:
|
|
752
|
+
# Stop subscription
|
|
753
|
+
self.subscription_manager.stop_subscription()
|
|
754
|
+
|
|
755
|
+
# Disconnect from broker
|
|
756
|
+
self.connection_service.disconnect()
|
|
757
|
+
|
|
758
|
+
# Save messages
|
|
759
|
+
self.message_storage.save_messages()
|
|
760
|
+
|
|
761
|
+
# Log final statistics
|
|
762
|
+
self._log_final_statistics()
|
|
763
|
+
|
|
764
|
+
except Exception as e:
|
|
765
|
+
logger.error(f"Error during cleanup: {e}")
|
|
766
|
+
|
|
767
|
+
def _log_final_statistics(self) -> None:
|
|
768
|
+
"""Log final processing statistics."""
|
|
769
|
+
runtime = time.time() - self.start_time
|
|
770
|
+
processor_stats = self.message_processor.get_stats()
|
|
771
|
+
|
|
772
|
+
logger.info("=== SUBSCRIBER STATISTICS ===")
|
|
773
|
+
logger.info(f"Runtime: {runtime:.2f} seconds")
|
|
774
|
+
logger.info(f"Messages received: {self.messages_received}")
|
|
775
|
+
logger.info(f"Messages processed: {self.messages_processed}")
|
|
776
|
+
logger.info(f"Messages stored: {self.message_storage.get_message_count()}")
|
|
777
|
+
logger.info(f"Processing errors: {processor_stats['error_count']}")
|
|
778
|
+
logger.info(
|
|
779
|
+
f"Active tasks remaining: {self.task_tracker.get_active_task_count()}"
|
|
780
|
+
)
|
|
781
|
+
logger.info(f"Tasks completed: {self.task_tracker.get_completed_task_count()}")
|
|
782
|
+
logger.info("=============================")
|
|
783
|
+
|
|
784
|
+
# Backward compatibility properties
|
|
785
|
+
@property
|
|
786
|
+
def active_tasks(self) -> Set[str]:
|
|
787
|
+
"""Get active tasks set for backward compatibility."""
|
|
788
|
+
return self.task_tracker.active_tasks
|
|
789
|
+
|
|
790
|
+
@property
|
|
791
|
+
def messages(self) -> List[Dict[str, Any]]:
|
|
792
|
+
"""Get messages list for backward compatibility."""
|
|
793
|
+
return [msg.to_dict() for msg in self.message_storage.messages]
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
# Backward compatibility alias
|
|
797
|
+
Subscriber = MessageSubscriber
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def create_subscriber_from_env(
|
|
801
|
+
namespace: str,
|
|
802
|
+
active_tasks: Set[str],
|
|
803
|
+
wave_complete_event: Optional[threading.Event] = None,
|
|
804
|
+
subscription_ready_event: Optional[threading.Event] = None,
|
|
805
|
+
results_path: str = ".",
|
|
806
|
+
) -> MessageSubscriber:
|
|
807
|
+
"""
|
|
808
|
+
Factory function to create a subscriber with environment-based configuration.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
namespace: The namespace for topic subscription
|
|
812
|
+
active_tasks: Set of active task IDs to track
|
|
813
|
+
wave_complete_event: Event to set when all tasks complete
|
|
814
|
+
subscription_ready_event: Event to set when subscription is ready
|
|
815
|
+
results_path: Path to save results
|
|
816
|
+
|
|
817
|
+
Returns:
|
|
818
|
+
Configured MessageSubscriber instance
|
|
819
|
+
"""
|
|
820
|
+
return MessageSubscriber(
|
|
821
|
+
namespace=namespace,
|
|
822
|
+
active_tasks=active_tasks,
|
|
823
|
+
wave_complete_event=wave_complete_event,
|
|
824
|
+
subscription_ready_event=subscription_ready_event,
|
|
825
|
+
results_path=results_path,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def main():
|
|
830
|
+
"""Main entry point for testing the subscriber."""
|
|
831
|
+
import signal
|
|
832
|
+
import sys
|
|
833
|
+
|
|
834
|
+
# Set up signal handling for graceful shutdown
|
|
835
|
+
def signal_handler(signum, frame):
|
|
836
|
+
print("\nShutting down subscriber...")
|
|
837
|
+
if "subscriber" in locals():
|
|
838
|
+
subscriber.stop()
|
|
839
|
+
subscriber.join()
|
|
840
|
+
sys.exit(0)
|
|
841
|
+
|
|
842
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
843
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
844
|
+
|
|
845
|
+
# Create test subscriber
|
|
846
|
+
active_tasks = set()
|
|
847
|
+
subscription_ready = threading.Event()
|
|
848
|
+
|
|
849
|
+
try:
|
|
850
|
+
subscriber = create_subscriber_from_env(
|
|
851
|
+
namespace="test",
|
|
852
|
+
active_tasks=active_tasks,
|
|
853
|
+
subscription_ready_event=subscription_ready,
|
|
854
|
+
results_path=".",
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
subscriber.start()
|
|
858
|
+
|
|
859
|
+
# Wait for subscription to be ready
|
|
860
|
+
subscription_ready.wait(timeout=30)
|
|
861
|
+
print("Subscriber is ready and running...")
|
|
862
|
+
|
|
863
|
+
# Keep running until interrupted
|
|
864
|
+
subscriber.join()
|
|
865
|
+
|
|
866
|
+
except Exception as e:
|
|
867
|
+
print(f"Error running subscriber: {e}")
|
|
868
|
+
sys.exit(1)
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
if __name__ == "__main__":
|
|
872
|
+
main()
|