solace-agent-mesh 0.2.4__py3-none-any.whl → 1.0.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/adk_llm.txt +93 -0
- solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
- solace_agent_mesh/agent/adk/callbacks.py +1694 -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 +353 -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 +1469 -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 +3388 -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 +176 -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 +569 -0
- solace_agent_mesh/agent/tools/image_tools.py +1184 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +289 -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.23bccffb.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.2c2e152c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/fbfa3e75.aca209c9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/main.7ed3319f.js.LICENSE.txt +81 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.d9520ae2.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 +60 -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-1753813536522.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -0
- solace_agent_mesh/assets/docs/search-doc-1753813536522.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 +197 -0
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +365 -0
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +387 -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 +138 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +309 -0
- solace_agent_mesh/cli/commands/plugin_cmd/official_registry.py +174 -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-An0a5j5k.js +663 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Bu5-4Bac.css +1 -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/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 +23 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/models.py +49 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +160 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog/scraper.py +525 -0
- solace_agent_mesh/config_portal/backend/plugin_catalog_server.py +216 -0
- solace_agent_mesh/config_portal/backend/server.py +550 -181
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-DNxCwAGB.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-d2b54a97.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 +821 -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 +400 -0
- solace_agent_mesh/templates/gateway_config_template.yaml +43 -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.1.dist-info/METADATA +432 -0
- solace_agent_mesh-1.0.1.dist-info/RECORD +359 -0
- solace_agent_mesh-1.0.1.dist-info/entry_points.txt +3 -0
- {solace_agent_mesh-0.2.4.dist-info → solace_agent_mesh-1.0.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,1542 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Component class for Gateway implementations in the Solace AI Connector.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import queue
|
|
7
|
+
import re
|
|
8
|
+
import threading
|
|
9
|
+
import base64
|
|
10
|
+
import uuid
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any, Dict, Optional, List, Tuple, Union
|
|
13
|
+
from urllib.parse import urlparse, parse_qs
|
|
14
|
+
|
|
15
|
+
from solace_ai_connector.components.component_base import ComponentBase
|
|
16
|
+
from solace_ai_connector.common.log import log
|
|
17
|
+
from google.adk.artifacts import BaseArtifactService
|
|
18
|
+
|
|
19
|
+
from ...common.agent_registry import AgentRegistry
|
|
20
|
+
from ...core_a2a.service import CoreA2AService
|
|
21
|
+
from ...agent.adk.services import initialize_artifact_service
|
|
22
|
+
from ...common.services.identity_service import (
|
|
23
|
+
BaseIdentityService,
|
|
24
|
+
create_identity_service,
|
|
25
|
+
)
|
|
26
|
+
from .task_context import TaskContextManager
|
|
27
|
+
from ...common.types import (
|
|
28
|
+
Part as A2APart,
|
|
29
|
+
Message as A2AMessage,
|
|
30
|
+
AgentCard,
|
|
31
|
+
JSONRPCResponse,
|
|
32
|
+
Task,
|
|
33
|
+
TaskStatusUpdateEvent,
|
|
34
|
+
TaskArtifactUpdateEvent,
|
|
35
|
+
JSONRPCError,
|
|
36
|
+
TextPart,
|
|
37
|
+
TaskStatus,
|
|
38
|
+
TaskState,
|
|
39
|
+
FilePart,
|
|
40
|
+
DataPart,
|
|
41
|
+
Artifact as A2AArtifact,
|
|
42
|
+
)
|
|
43
|
+
from ...common.a2a_protocol import (
|
|
44
|
+
get_gateway_response_topic,
|
|
45
|
+
get_gateway_response_subscription_topic,
|
|
46
|
+
get_gateway_status_topic,
|
|
47
|
+
get_gateway_status_subscription_topic,
|
|
48
|
+
get_discovery_topic,
|
|
49
|
+
_topic_matches_subscription,
|
|
50
|
+
_subscription_to_regex,
|
|
51
|
+
)
|
|
52
|
+
from ...common.utils import is_text_based_mime_type
|
|
53
|
+
from ...common.utils.embeds import (
|
|
54
|
+
resolve_embeds_in_string,
|
|
55
|
+
resolve_embeds_recursively_in_string,
|
|
56
|
+
evaluate_embed,
|
|
57
|
+
LATE_EMBED_TYPES,
|
|
58
|
+
EARLY_EMBED_TYPES,
|
|
59
|
+
EMBED_DELIMITER_OPEN,
|
|
60
|
+
)
|
|
61
|
+
from solace_ai_connector.common.message import (
|
|
62
|
+
Message as SolaceMessage,
|
|
63
|
+
)
|
|
64
|
+
from solace_ai_connector.common.event import Event, EventType
|
|
65
|
+
from abc import abstractmethod
|
|
66
|
+
|
|
67
|
+
from ...common.middleware.registry import MiddlewareRegistry
|
|
68
|
+
from ...agent.utils.artifact_helpers import load_artifact_content_or_metadata
|
|
69
|
+
|
|
70
|
+
info = {
|
|
71
|
+
"class_name": "BaseGatewayComponent",
|
|
72
|
+
"description": (
|
|
73
|
+
"Abstract base component for A2A gateways. Handles common service "
|
|
74
|
+
"initialization and provides a framework for platform-specific logic. "
|
|
75
|
+
"Configuration is typically derived from the parent BaseGatewayApp's app_config."
|
|
76
|
+
),
|
|
77
|
+
"config_parameters": [],
|
|
78
|
+
"input_schema": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"description": "Not typically used directly; component reacts to events from its input queue.",
|
|
81
|
+
},
|
|
82
|
+
"output_schema": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"description": "Not typically used directly; component sends data to external systems.",
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BaseGatewayComponent(ComponentBase):
|
|
90
|
+
"""
|
|
91
|
+
Abstract base class for Gateway components.
|
|
92
|
+
|
|
93
|
+
Initializes shared services and manages the core lifecycle for processing
|
|
94
|
+
A2A messages and interacting with an external communication platform.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
_RESOLVE_EMBEDS_IN_FINAL_RESPONSE = False
|
|
98
|
+
|
|
99
|
+
def get_config(self, key: str, default: Any = None) -> Any:
|
|
100
|
+
"""
|
|
101
|
+
Overrides the default get_config to first look inside the nested
|
|
102
|
+
'app_config' dictionary that BaseGatewayApp places in the component_config.
|
|
103
|
+
This is the primary way gateway components should access their configuration.
|
|
104
|
+
"""
|
|
105
|
+
if "app_config" in self.component_config:
|
|
106
|
+
value = self.component_config["app_config"].get(key)
|
|
107
|
+
if value is not None:
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
return super().get_config(key, default)
|
|
111
|
+
|
|
112
|
+
def __init__(self, **kwargs: Any):
|
|
113
|
+
super().__init__(info, **kwargs)
|
|
114
|
+
log.info("%s Initializing Base Gateway Component...", self.log_identifier)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
self.namespace: str = self.get_config("namespace")
|
|
118
|
+
self.gateway_id: str = self.get_config("gateway_id")
|
|
119
|
+
if not self.namespace or not self.gateway_id:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"Namespace and Gateway ID must be configured in the app_config."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
self.enable_embed_resolution: bool = self.get_config(
|
|
125
|
+
"enable_embed_resolution", True
|
|
126
|
+
)
|
|
127
|
+
self.gateway_max_artifact_resolve_size_bytes: int = self.get_config(
|
|
128
|
+
"gateway_max_artifact_resolve_size_bytes"
|
|
129
|
+
)
|
|
130
|
+
self.gateway_recursive_embed_depth: int = self.get_config(
|
|
131
|
+
"gateway_recursive_embed_depth"
|
|
132
|
+
)
|
|
133
|
+
self.gateway_artifact_content_limit_bytes: int = self.get_config(
|
|
134
|
+
"gateway_artifact_content_limit_bytes"
|
|
135
|
+
)
|
|
136
|
+
_ = self.get_config("artifact_service")
|
|
137
|
+
|
|
138
|
+
log.info(
|
|
139
|
+
"%s Retrieved common configs: Namespace=%s, GatewayID=%s",
|
|
140
|
+
self.log_identifier,
|
|
141
|
+
self.namespace,
|
|
142
|
+
self.gateway_id,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
log.error(
|
|
147
|
+
"%s Failed to retrieve essential configuration: %s",
|
|
148
|
+
self.log_identifier,
|
|
149
|
+
e,
|
|
150
|
+
)
|
|
151
|
+
raise ValueError(f"Configuration retrieval error: {e}") from e
|
|
152
|
+
|
|
153
|
+
self.agent_registry: AgentRegistry = AgentRegistry()
|
|
154
|
+
self.core_a2a_service: CoreA2AService = CoreA2AService(
|
|
155
|
+
agent_registry=self.agent_registry, namespace=self.namespace
|
|
156
|
+
)
|
|
157
|
+
self.shared_artifact_service: Optional[BaseArtifactService] = (
|
|
158
|
+
initialize_artifact_service(self)
|
|
159
|
+
)
|
|
160
|
+
self.task_context_manager: TaskContextManager = TaskContextManager()
|
|
161
|
+
self.internal_event_queue: queue.Queue = queue.Queue()
|
|
162
|
+
self.message_processor_thread: Optional[threading.Thread] = None
|
|
163
|
+
self.async_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
164
|
+
self.async_thread: Optional[threading.Thread] = None
|
|
165
|
+
|
|
166
|
+
identity_service_config = self.get_config("identity_service")
|
|
167
|
+
self.identity_service: Optional[BaseIdentityService] = create_identity_service(
|
|
168
|
+
identity_service_config
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self._config_resolver = MiddlewareRegistry.get_config_resolver()
|
|
172
|
+
log.info(
|
|
173
|
+
"%s Middleware system initialized (using default configuration resolver).",
|
|
174
|
+
self.log_identifier,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
log.info(
|
|
178
|
+
"%s Base Gateway Component initialized successfully.", self.log_identifier
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def publish_a2a_message(
|
|
182
|
+
self, topic: str, payload: Dict, user_properties: Optional[Dict] = None
|
|
183
|
+
) -> None:
|
|
184
|
+
log.debug(
|
|
185
|
+
"%s Publishing A2A message to topic: %s via App", self.log_identifier, topic
|
|
186
|
+
)
|
|
187
|
+
try:
|
|
188
|
+
app = self.get_app()
|
|
189
|
+
if app:
|
|
190
|
+
app.send_message(
|
|
191
|
+
payload=payload, topic=topic, user_properties=user_properties
|
|
192
|
+
)
|
|
193
|
+
log.debug(
|
|
194
|
+
"%s Successfully published message to %s via App",
|
|
195
|
+
self.log_identifier,
|
|
196
|
+
topic,
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
log.error(
|
|
200
|
+
"%s Cannot publish message: Not running within a SAC App context.",
|
|
201
|
+
self.log_identifier,
|
|
202
|
+
)
|
|
203
|
+
raise RuntimeError(
|
|
204
|
+
"Cannot publish message: Not running within a SAC App context."
|
|
205
|
+
)
|
|
206
|
+
except Exception as e:
|
|
207
|
+
log.exception(
|
|
208
|
+
"%s Failed to publish A2A message to topic %s via App: %s",
|
|
209
|
+
self.log_identifier,
|
|
210
|
+
topic,
|
|
211
|
+
e,
|
|
212
|
+
)
|
|
213
|
+
raise
|
|
214
|
+
|
|
215
|
+
async def authenticate_and_enrich_user(
|
|
216
|
+
self, external_event_data: Any
|
|
217
|
+
) -> Optional[Dict[str, Any]]:
|
|
218
|
+
"""
|
|
219
|
+
Orchestrates the full authentication and identity enrichment flow.
|
|
220
|
+
This method should be called by gateway handlers.
|
|
221
|
+
"""
|
|
222
|
+
log_id_prefix = f"{self.log_identifier}[AuthAndEnrich]"
|
|
223
|
+
|
|
224
|
+
auth_claims = await self._extract_initial_claims(external_event_data)
|
|
225
|
+
if not auth_claims:
|
|
226
|
+
log.warning(
|
|
227
|
+
"%s Initial claims extraction failed or returned no identity.",
|
|
228
|
+
log_id_prefix,
|
|
229
|
+
)
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
if self.identity_service:
|
|
233
|
+
enriched_profile = await self.identity_service.get_user_profile(auth_claims)
|
|
234
|
+
if enriched_profile:
|
|
235
|
+
final_profile = enriched_profile.copy()
|
|
236
|
+
final_profile.update(auth_claims)
|
|
237
|
+
log.info(
|
|
238
|
+
"%s Successfully merged auth claims and enriched profile for user: %s",
|
|
239
|
+
log_id_prefix,
|
|
240
|
+
auth_claims.get("id"),
|
|
241
|
+
)
|
|
242
|
+
return final_profile
|
|
243
|
+
else:
|
|
244
|
+
log.debug(
|
|
245
|
+
"%s IdentityService found no profile for user: %s. Using claims only.",
|
|
246
|
+
log_id_prefix,
|
|
247
|
+
auth_claims.get("id"),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
return auth_claims
|
|
251
|
+
|
|
252
|
+
async def submit_a2a_task(
|
|
253
|
+
self,
|
|
254
|
+
target_agent_name: str,
|
|
255
|
+
a2a_parts: List[A2APart],
|
|
256
|
+
external_request_context: Dict[str, Any],
|
|
257
|
+
user_identity: Any,
|
|
258
|
+
is_streaming: bool = True,
|
|
259
|
+
api_version: str = "v2",
|
|
260
|
+
) -> str:
|
|
261
|
+
log_id_prefix = f"{self.log_identifier}[SubmitA2ATask]"
|
|
262
|
+
log.info(
|
|
263
|
+
"%s Submitting task for user_identity: %s",
|
|
264
|
+
log_id_prefix,
|
|
265
|
+
user_identity.get("id", user_identity),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if not isinstance(user_identity, dict) or not user_identity.get("id"):
|
|
269
|
+
log.error(
|
|
270
|
+
"%s Authentication failed or returned invalid profile. Denying task submission.",
|
|
271
|
+
log_id_prefix,
|
|
272
|
+
)
|
|
273
|
+
raise PermissionError("User not authenticated or identity is invalid.")
|
|
274
|
+
|
|
275
|
+
force_identity_str = self.get_config("force_user_identity")
|
|
276
|
+
if force_identity_str:
|
|
277
|
+
original_identity_id = user_identity.get("id")
|
|
278
|
+
user_identity = {"id": force_identity_str, "name": force_identity_str}
|
|
279
|
+
log.warning(
|
|
280
|
+
"%s DEVELOPMENT MODE: Forcing user_identity from '%s' to '%s'",
|
|
281
|
+
log_id_prefix,
|
|
282
|
+
original_identity_id,
|
|
283
|
+
force_identity_str,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
config_resolver = MiddlewareRegistry.get_config_resolver()
|
|
287
|
+
gateway_context = {"gateway_id": self.gateway_id}
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
user_config = await config_resolver.resolve_user_config(
|
|
291
|
+
user_identity, gateway_context, {}
|
|
292
|
+
)
|
|
293
|
+
log.info(
|
|
294
|
+
"%s Resolved user configuration for user_identity '%s': %s",
|
|
295
|
+
log_id_prefix,
|
|
296
|
+
user_identity.get("id"),
|
|
297
|
+
{k: v for k, v in user_config.items() if not k.startswith("_")},
|
|
298
|
+
)
|
|
299
|
+
except Exception as config_err:
|
|
300
|
+
log.exception(
|
|
301
|
+
"%s Error resolving user configuration for '%s': %s. Proceeding with default configuration.",
|
|
302
|
+
log_id_prefix,
|
|
303
|
+
user_identity.get("id"),
|
|
304
|
+
config_err,
|
|
305
|
+
)
|
|
306
|
+
user_config = {}
|
|
307
|
+
|
|
308
|
+
user_config["user_profile"] = user_identity
|
|
309
|
+
|
|
310
|
+
external_request_context["user_identity"] = user_identity
|
|
311
|
+
external_request_context["a2a_user_config"] = user_config
|
|
312
|
+
external_request_context["api_version"] = api_version
|
|
313
|
+
log.debug(
|
|
314
|
+
"%s Stored user_identity, configuration, and api_version (%s) in external_request_context.",
|
|
315
|
+
log_id_prefix,
|
|
316
|
+
api_version,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
now = datetime.now(timezone.utc)
|
|
320
|
+
timestamp_str = now.isoformat()
|
|
321
|
+
timestamp_header_part = TextPart(
|
|
322
|
+
text=f"Request received by gateway at: {timestamp_str}"
|
|
323
|
+
)
|
|
324
|
+
if not isinstance(a2a_parts, list):
|
|
325
|
+
a2a_parts = list(a2a_parts)
|
|
326
|
+
a2a_parts.insert(0, timestamp_header_part)
|
|
327
|
+
log.debug("%s Prepended timestamp to a2a_parts.", log_id_prefix)
|
|
328
|
+
|
|
329
|
+
a2a_session_id = external_request_context.get("a2a_session_id")
|
|
330
|
+
user_id_for_a2a = external_request_context.get(
|
|
331
|
+
"user_id_for_a2a", user_identity.get("id")
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if not a2a_session_id:
|
|
335
|
+
a2a_session_id = f"gdk-session-{uuid.uuid4().hex}"
|
|
336
|
+
log.warning(
|
|
337
|
+
"%s 'a2a_session_id' not found in external_request_context, generated: %s",
|
|
338
|
+
self.log_identifier,
|
|
339
|
+
a2a_session_id,
|
|
340
|
+
)
|
|
341
|
+
external_request_context["a2a_session_id"] = a2a_session_id
|
|
342
|
+
|
|
343
|
+
a2a_message = A2AMessage(role="user", parts=a2a_parts)
|
|
344
|
+
reply_topic_pattern = get_gateway_response_topic(
|
|
345
|
+
self.namespace, self.gateway_id, "{task_id}"
|
|
346
|
+
)
|
|
347
|
+
status_topic_pattern = get_gateway_status_topic(
|
|
348
|
+
self.namespace, self.gateway_id, "{task_id}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
task_metadata_override: Dict[str, Any] = {}
|
|
352
|
+
system_purpose = self.get_config("system_purpose", "")
|
|
353
|
+
response_format = self.get_config("response_format", "")
|
|
354
|
+
|
|
355
|
+
if system_purpose:
|
|
356
|
+
task_metadata_override["system_purpose"] = system_purpose
|
|
357
|
+
log.debug("%s Adding system_purpose to task metadata.", log_id_prefix)
|
|
358
|
+
if response_format:
|
|
359
|
+
task_metadata_override["response_format"] = response_format
|
|
360
|
+
log.debug("%s Adding response_format to task metadata.", log_id_prefix)
|
|
361
|
+
|
|
362
|
+
if is_streaming:
|
|
363
|
+
target_topic, payload, user_properties = (
|
|
364
|
+
self.core_a2a_service.submit_streaming_task(
|
|
365
|
+
agent_name=target_agent_name,
|
|
366
|
+
a2a_message=a2a_message,
|
|
367
|
+
session_id=a2a_session_id,
|
|
368
|
+
client_id=self.gateway_id,
|
|
369
|
+
reply_to_topic=reply_topic_pattern,
|
|
370
|
+
status_to_topic=status_topic_pattern,
|
|
371
|
+
user_id=user_id_for_a2a,
|
|
372
|
+
a2a_user_config=user_config,
|
|
373
|
+
metadata_override=task_metadata_override,
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
target_topic, payload, user_properties = self.core_a2a_service.submit_task(
|
|
378
|
+
agent_name=target_agent_name,
|
|
379
|
+
a2a_message=a2a_message,
|
|
380
|
+
session_id=a2a_session_id,
|
|
381
|
+
client_id=self.gateway_id,
|
|
382
|
+
reply_to_topic=reply_topic_pattern,
|
|
383
|
+
user_id=user_id_for_a2a,
|
|
384
|
+
a2a_user_config=user_config,
|
|
385
|
+
metadata_override=task_metadata_override,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
task_id = payload.get("params", {}).get("id")
|
|
389
|
+
if not task_id:
|
|
390
|
+
log.error(
|
|
391
|
+
"%s CoreA2AService did not return a task ID in the payload.",
|
|
392
|
+
log_id_prefix,
|
|
393
|
+
)
|
|
394
|
+
raise ValueError("CoreA2AService did not return a task ID in the payload.")
|
|
395
|
+
|
|
396
|
+
if user_properties is None:
|
|
397
|
+
user_properties = {}
|
|
398
|
+
|
|
399
|
+
user_properties["replyTo"] = get_gateway_response_topic(
|
|
400
|
+
self.namespace, self.gateway_id, task_id
|
|
401
|
+
)
|
|
402
|
+
if is_streaming:
|
|
403
|
+
user_properties["a2aStatusTopic"] = get_gateway_status_topic(
|
|
404
|
+
self.namespace, self.gateway_id, task_id
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
self.task_context_manager.store_context(task_id, external_request_context)
|
|
408
|
+
log.info("%s Stored external context for task_id: %s", log_id_prefix, task_id)
|
|
409
|
+
|
|
410
|
+
self.publish_a2a_message(
|
|
411
|
+
topic=target_topic, payload=payload, user_properties=user_properties
|
|
412
|
+
)
|
|
413
|
+
log.info(
|
|
414
|
+
"%s Submitted A2A task %s to agent %s. Streaming: %s",
|
|
415
|
+
log_id_prefix,
|
|
416
|
+
task_id,
|
|
417
|
+
target_agent_name,
|
|
418
|
+
is_streaming,
|
|
419
|
+
)
|
|
420
|
+
return task_id
|
|
421
|
+
|
|
422
|
+
def process_event(self, event: Event):
|
|
423
|
+
if event.event_type == EventType.MESSAGE:
|
|
424
|
+
original_broker_message: Optional[SolaceMessage] = event.data
|
|
425
|
+
if not original_broker_message:
|
|
426
|
+
log.warning(
|
|
427
|
+
"%s Received MESSAGE event with no data. Ignoring.",
|
|
428
|
+
self.log_identifier,
|
|
429
|
+
)
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
log.debug(
|
|
433
|
+
"%s Received SolaceMessage on topic: %s. Bridging to internal queue.",
|
|
434
|
+
self.log_identifier,
|
|
435
|
+
original_broker_message.get_topic(),
|
|
436
|
+
)
|
|
437
|
+
try:
|
|
438
|
+
msg_data_for_processor = {
|
|
439
|
+
"topic": original_broker_message.get_topic(),
|
|
440
|
+
"payload": original_broker_message.get_payload(),
|
|
441
|
+
"user_properties": original_broker_message.get_user_properties(),
|
|
442
|
+
"_original_broker_message": original_broker_message,
|
|
443
|
+
}
|
|
444
|
+
self.internal_event_queue.put_nowait(msg_data_for_processor)
|
|
445
|
+
except queue.Full:
|
|
446
|
+
log.error(
|
|
447
|
+
"%s Internal event queue full. Cannot bridge message. NACKing.",
|
|
448
|
+
self.log_identifier,
|
|
449
|
+
)
|
|
450
|
+
original_broker_message.call_negative_acknowledgements()
|
|
451
|
+
except Exception as e:
|
|
452
|
+
log.exception(
|
|
453
|
+
"%s Error bridging message to internal queue: %s. NACKing.",
|
|
454
|
+
self.log_identifier,
|
|
455
|
+
e,
|
|
456
|
+
)
|
|
457
|
+
original_broker_message.call_negative_acknowledgements()
|
|
458
|
+
else:
|
|
459
|
+
log.debug(
|
|
460
|
+
"%s Received non-MESSAGE event type: %s. Passing to super.",
|
|
461
|
+
self.log_identifier,
|
|
462
|
+
event.event_type,
|
|
463
|
+
)
|
|
464
|
+
super().process_event(event)
|
|
465
|
+
|
|
466
|
+
async def _handle_resolved_signals(
|
|
467
|
+
self,
|
|
468
|
+
external_request_context: Dict,
|
|
469
|
+
signals: List[Tuple[int, Any]],
|
|
470
|
+
original_rpc_id: Optional[str],
|
|
471
|
+
is_finalizing_context: bool = False,
|
|
472
|
+
):
|
|
473
|
+
log_id_prefix = f"{self.log_identifier}[SignalHandler]"
|
|
474
|
+
if not signals:
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
for _, signal_tuple in signals:
|
|
478
|
+
if (
|
|
479
|
+
isinstance(signal_tuple, tuple)
|
|
480
|
+
and len(signal_tuple) == 3
|
|
481
|
+
and signal_tuple[0] is None
|
|
482
|
+
):
|
|
483
|
+
signal_type = signal_tuple[1]
|
|
484
|
+
signal_data = signal_tuple[2]
|
|
485
|
+
|
|
486
|
+
if signal_type == "SIGNAL_STATUS_UPDATE":
|
|
487
|
+
status_text = signal_data
|
|
488
|
+
log.info(
|
|
489
|
+
"%s Handling SIGNAL_STATUS_UPDATE: '%s'",
|
|
490
|
+
log_id_prefix,
|
|
491
|
+
status_text,
|
|
492
|
+
)
|
|
493
|
+
if is_finalizing_context:
|
|
494
|
+
log.debug(
|
|
495
|
+
"%s Suppressing SIGNAL_STATUS_UPDATE ('%s') during finalizing context.",
|
|
496
|
+
log_id_prefix,
|
|
497
|
+
status_text,
|
|
498
|
+
)
|
|
499
|
+
continue
|
|
500
|
+
try:
|
|
501
|
+
signal_data_part = DataPart(
|
|
502
|
+
data={"type": "agent_status", "text": status_text},
|
|
503
|
+
metadata={"source": "agent_progress_update"},
|
|
504
|
+
)
|
|
505
|
+
signal_a2a_message = A2AMessage(
|
|
506
|
+
role="agent", parts=[signal_data_part]
|
|
507
|
+
)
|
|
508
|
+
signal_task_status = TaskStatus(
|
|
509
|
+
state=TaskState.WORKING, message=signal_a2a_message
|
|
510
|
+
)
|
|
511
|
+
a2a_task_id_for_signal = external_request_context.get(
|
|
512
|
+
"a2a_task_id_for_event", original_rpc_id
|
|
513
|
+
)
|
|
514
|
+
if not a2a_task_id_for_signal:
|
|
515
|
+
log.error(
|
|
516
|
+
"%s Cannot determine A2A task ID for signal event. Skipping.",
|
|
517
|
+
log_id_prefix,
|
|
518
|
+
)
|
|
519
|
+
continue
|
|
520
|
+
|
|
521
|
+
signal_event = TaskStatusUpdateEvent(
|
|
522
|
+
id=a2a_task_id_for_signal,
|
|
523
|
+
status=signal_task_status,
|
|
524
|
+
final=False,
|
|
525
|
+
)
|
|
526
|
+
await self._send_update_to_external(
|
|
527
|
+
external_request_context=external_request_context,
|
|
528
|
+
event_data=signal_event,
|
|
529
|
+
is_final_chunk_of_update=True,
|
|
530
|
+
)
|
|
531
|
+
log.debug(
|
|
532
|
+
"%s Sent status signal as TaskStatusUpdateEvent.",
|
|
533
|
+
log_id_prefix,
|
|
534
|
+
)
|
|
535
|
+
except Exception as e:
|
|
536
|
+
log.exception(
|
|
537
|
+
"%s Error sending status signal: %s", log_id_prefix, e
|
|
538
|
+
)
|
|
539
|
+
else:
|
|
540
|
+
log.warning(
|
|
541
|
+
"%s Received unhandled signal type during embed resolution: %s",
|
|
542
|
+
log_id_prefix,
|
|
543
|
+
signal_type,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
async def _resolve_uri_in_file_part(self, part: A2APart):
|
|
547
|
+
"""
|
|
548
|
+
Checks if a part is a FilePart with a resolvable URI and, if so,
|
|
549
|
+
resolves it and mutates the part in-place.
|
|
550
|
+
"""
|
|
551
|
+
if not (
|
|
552
|
+
isinstance(part, FilePart)
|
|
553
|
+
and part.file
|
|
554
|
+
and part.file.uri
|
|
555
|
+
and part.file.uri.startswith("artifact://")
|
|
556
|
+
):
|
|
557
|
+
return
|
|
558
|
+
|
|
559
|
+
if not self.shared_artifact_service:
|
|
560
|
+
log.warning(
|
|
561
|
+
"%s Cannot resolve artifact URI, shared_artifact_service is not configured.",
|
|
562
|
+
self.log_identifier,
|
|
563
|
+
)
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
uri = part.file.uri
|
|
567
|
+
log_id_prefix = f"{self.log_identifier}[ResolveURI]"
|
|
568
|
+
try:
|
|
569
|
+
log.info("%s Found artifact URI to resolve: %s", log_id_prefix, uri)
|
|
570
|
+
parsed_uri = urlparse(uri)
|
|
571
|
+
app_name = parsed_uri.netloc
|
|
572
|
+
path_parts = parsed_uri.path.strip("/").split("/")
|
|
573
|
+
|
|
574
|
+
if not app_name or len(path_parts) != 3:
|
|
575
|
+
raise ValueError(
|
|
576
|
+
"Invalid URI structure. Expected artifact://app_name/user_id/session_id/filename"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
user_id, session_id, filename = path_parts
|
|
580
|
+
version = int(parse_qs(parsed_uri.query).get("version", [None])[0])
|
|
581
|
+
|
|
582
|
+
loaded_artifact = await load_artifact_content_or_metadata(
|
|
583
|
+
artifact_service=self.shared_artifact_service,
|
|
584
|
+
app_name=app_name,
|
|
585
|
+
user_id=user_id,
|
|
586
|
+
session_id=session_id,
|
|
587
|
+
filename=filename,
|
|
588
|
+
version=version,
|
|
589
|
+
return_raw_bytes=True,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
if loaded_artifact.get("status") == "success":
|
|
593
|
+
content_bytes = loaded_artifact.get("raw_bytes")
|
|
594
|
+
part.file.bytes = base64.b64encode(content_bytes).decode("utf-8")
|
|
595
|
+
part.file.uri = None
|
|
596
|
+
log.info(
|
|
597
|
+
"%s Successfully resolved and embedded artifact: %s",
|
|
598
|
+
log_id_prefix,
|
|
599
|
+
uri,
|
|
600
|
+
)
|
|
601
|
+
else:
|
|
602
|
+
log.error(
|
|
603
|
+
"%s Failed to resolve artifact URI '%s': %s",
|
|
604
|
+
log_id_prefix,
|
|
605
|
+
uri,
|
|
606
|
+
loaded_artifact.get("message"),
|
|
607
|
+
)
|
|
608
|
+
except Exception as e:
|
|
609
|
+
log.exception(
|
|
610
|
+
"%s Error resolving artifact URI '%s': %s", log_id_prefix, uri, e
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
async def _resolve_uris_in_parts_list(self, parts: List[A2APart]):
|
|
614
|
+
"""Iterates over a list of A2APart objects and resolves any FilePart URIs."""
|
|
615
|
+
if not parts:
|
|
616
|
+
return
|
|
617
|
+
for part in parts:
|
|
618
|
+
await self._resolve_uri_in_file_part(part)
|
|
619
|
+
|
|
620
|
+
async def _resolve_uris_in_payload(self, parsed_event: Any):
|
|
621
|
+
"""
|
|
622
|
+
Dispatcher that calls the appropriate targeted URI resolver based on the
|
|
623
|
+
Pydantic model type of the event.
|
|
624
|
+
"""
|
|
625
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent):
|
|
626
|
+
if parsed_event.status and parsed_event.status.message:
|
|
627
|
+
await self._resolve_uris_in_parts_list(
|
|
628
|
+
parsed_event.status.message.parts
|
|
629
|
+
)
|
|
630
|
+
elif isinstance(parsed_event, TaskArtifactUpdateEvent):
|
|
631
|
+
if parsed_event.artifact:
|
|
632
|
+
await self._resolve_uris_in_parts_list(parsed_event.artifact.parts)
|
|
633
|
+
elif isinstance(parsed_event, Task):
|
|
634
|
+
if parsed_event.status and parsed_event.status.message:
|
|
635
|
+
await self._resolve_uris_in_parts_list(
|
|
636
|
+
parsed_event.status.message.parts
|
|
637
|
+
)
|
|
638
|
+
if parsed_event.artifacts:
|
|
639
|
+
for artifact in parsed_event.artifacts:
|
|
640
|
+
await self._resolve_uris_in_parts_list(artifact.parts)
|
|
641
|
+
else:
|
|
642
|
+
log.debug(
|
|
643
|
+
"%s Payload type '%s' does not support targeted URI resolution. Skipping.",
|
|
644
|
+
self.log_identifier,
|
|
645
|
+
type(parsed_event).__name__,
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
async def _handle_discovery_message(self, payload: Dict) -> bool:
|
|
649
|
+
"""Handles incoming agent discovery messages."""
|
|
650
|
+
try:
|
|
651
|
+
agent_card = AgentCard(**payload)
|
|
652
|
+
self.core_a2a_service.process_discovery_message(agent_card)
|
|
653
|
+
return True
|
|
654
|
+
except Exception as e:
|
|
655
|
+
log.error(
|
|
656
|
+
"%s Failed to process discovery message: %s. Payload: %s",
|
|
657
|
+
self.log_identifier,
|
|
658
|
+
e,
|
|
659
|
+
payload,
|
|
660
|
+
)
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
def _extract_task_id_from_topic(
|
|
664
|
+
self, topic: str, subscription_pattern: str
|
|
665
|
+
) -> Optional[str]:
|
|
666
|
+
"""Extracts the task ID from the end of a topic string based on the subscription."""
|
|
667
|
+
base_regex_str = _subscription_to_regex(subscription_pattern).replace(r".*", "")
|
|
668
|
+
match = re.match(base_regex_str, topic)
|
|
669
|
+
if match:
|
|
670
|
+
task_id_part = topic[match.end() :]
|
|
671
|
+
task_id = task_id_part.lstrip("/")
|
|
672
|
+
if task_id:
|
|
673
|
+
log.debug(
|
|
674
|
+
"%s Extracted Task ID '%s' from topic '%s'",
|
|
675
|
+
self.log_identifier,
|
|
676
|
+
task_id,
|
|
677
|
+
topic,
|
|
678
|
+
)
|
|
679
|
+
return task_id
|
|
680
|
+
log.warning(
|
|
681
|
+
"%s Could not extract Task ID from topic '%s' using pattern '%s'",
|
|
682
|
+
self.log_identifier,
|
|
683
|
+
topic,
|
|
684
|
+
subscription_pattern,
|
|
685
|
+
)
|
|
686
|
+
return None
|
|
687
|
+
|
|
688
|
+
def _parse_a2a_event_from_rpc_result(
|
|
689
|
+
self, rpc_result: Dict, expected_task_id: Optional[str]
|
|
690
|
+
) -> Optional[Union[Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent]]:
|
|
691
|
+
"""
|
|
692
|
+
Parses the result field of a JSONRPCResponse into a specific A2A Pydantic model.
|
|
693
|
+
Verifies task ID if expected_task_id is provided.
|
|
694
|
+
"""
|
|
695
|
+
if not isinstance(rpc_result, dict):
|
|
696
|
+
log.error(
|
|
697
|
+
"%s RPC result is not a dictionary. Cannot parse.", self.log_identifier
|
|
698
|
+
)
|
|
699
|
+
return None
|
|
700
|
+
|
|
701
|
+
actual_task_id = rpc_result.get("id")
|
|
702
|
+
if expected_task_id and actual_task_id != expected_task_id:
|
|
703
|
+
log.error(
|
|
704
|
+
"%s Task ID mismatch! Expected: %s, Got from payload: %s.",
|
|
705
|
+
self.log_identifier,
|
|
706
|
+
expected_task_id,
|
|
707
|
+
actual_task_id,
|
|
708
|
+
)
|
|
709
|
+
return None
|
|
710
|
+
|
|
711
|
+
try:
|
|
712
|
+
if "status" in rpc_result and "final" in rpc_result:
|
|
713
|
+
return TaskStatusUpdateEvent(**rpc_result)
|
|
714
|
+
elif "artifact" in rpc_result:
|
|
715
|
+
return TaskArtifactUpdateEvent(**rpc_result)
|
|
716
|
+
elif "status" in rpc_result and "sessionId" in rpc_result:
|
|
717
|
+
return Task(**rpc_result)
|
|
718
|
+
else:
|
|
719
|
+
log.warning(
|
|
720
|
+
"%s Unknown result structure in RPC response for task %s: %s",
|
|
721
|
+
self.log_identifier,
|
|
722
|
+
actual_task_id or "unknown",
|
|
723
|
+
rpc_result,
|
|
724
|
+
)
|
|
725
|
+
return None
|
|
726
|
+
except Exception as e:
|
|
727
|
+
log.error(
|
|
728
|
+
"%s Failed to parse RPC result into A2A Pydantic model for task %s: %s. Result: %s",
|
|
729
|
+
self.log_identifier,
|
|
730
|
+
actual_task_id or "unknown",
|
|
731
|
+
e,
|
|
732
|
+
rpc_result,
|
|
733
|
+
)
|
|
734
|
+
return None
|
|
735
|
+
|
|
736
|
+
async def _resolve_embeds_and_handle_signals(
|
|
737
|
+
self,
|
|
738
|
+
event_with_parts: Union[TaskStatusUpdateEvent, Task, TaskArtifactUpdateEvent],
|
|
739
|
+
external_request_context: Dict[str, Any],
|
|
740
|
+
a2a_task_id: str,
|
|
741
|
+
original_rpc_id: Optional[str],
|
|
742
|
+
is_finalizing_context: bool = False,
|
|
743
|
+
) -> bool:
|
|
744
|
+
"""
|
|
745
|
+
Resolves embeds and handles signals for an event containing parts.
|
|
746
|
+
Modifies event_with_parts in place if text content changes.
|
|
747
|
+
Manages stream buffer for TaskStatusUpdateEvent.
|
|
748
|
+
Returns True if the event content was modified or signals were handled, False otherwise.
|
|
749
|
+
"""
|
|
750
|
+
if not self.enable_embed_resolution:
|
|
751
|
+
return False
|
|
752
|
+
|
|
753
|
+
log_id_prefix = f"{self.log_identifier}[EmbedResolve:{a2a_task_id}]"
|
|
754
|
+
content_modified_or_signal_handled = False
|
|
755
|
+
|
|
756
|
+
embed_eval_context = {
|
|
757
|
+
"artifact_service": self.shared_artifact_service,
|
|
758
|
+
"session_context": {
|
|
759
|
+
"app_name": external_request_context.get(
|
|
760
|
+
"app_name_for_artifacts", self.gateway_id
|
|
761
|
+
),
|
|
762
|
+
"user_id": external_request_context.get("user_id_for_artifacts"),
|
|
763
|
+
"session_id": external_request_context.get("a2a_session_id"),
|
|
764
|
+
},
|
|
765
|
+
}
|
|
766
|
+
embed_eval_config = {
|
|
767
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
768
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
parts_owner: Optional[Union[A2AMessage, A2AArtifact]] = None
|
|
772
|
+
is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
|
|
773
|
+
|
|
774
|
+
if isinstance(event_with_parts, (TaskStatusUpdateEvent, Task)):
|
|
775
|
+
if event_with_parts.status and event_with_parts.status.message:
|
|
776
|
+
parts_owner = event_with_parts.status.message
|
|
777
|
+
elif isinstance(event_with_parts, TaskArtifactUpdateEvent):
|
|
778
|
+
if event_with_parts.artifact:
|
|
779
|
+
parts_owner = event_with_parts.artifact
|
|
780
|
+
|
|
781
|
+
if parts_owner and parts_owner.parts:
|
|
782
|
+
new_parts_for_owner: List[A2APart] = []
|
|
783
|
+
stream_buffer_key = f"{a2a_task_id}_stream_buffer"
|
|
784
|
+
current_buffer = ""
|
|
785
|
+
|
|
786
|
+
if is_streaming_status_update:
|
|
787
|
+
current_buffer = (
|
|
788
|
+
self.task_context_manager.get_context(stream_buffer_key) or ""
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
for part_obj in parts_owner.parts:
|
|
792
|
+
if isinstance(part_obj, TextPart) and part_obj.text is not None:
|
|
793
|
+
text_to_resolve = part_obj.text
|
|
794
|
+
original_part_text = part_obj.text
|
|
795
|
+
|
|
796
|
+
if is_streaming_status_update:
|
|
797
|
+
current_buffer += part_obj.text
|
|
798
|
+
text_to_resolve = current_buffer
|
|
799
|
+
|
|
800
|
+
resolved_text, processed_idx, signals = (
|
|
801
|
+
await resolve_embeds_in_string(
|
|
802
|
+
text=text_to_resolve,
|
|
803
|
+
context=embed_eval_context,
|
|
804
|
+
resolver_func=evaluate_embed,
|
|
805
|
+
types_to_resolve=LATE_EMBED_TYPES.copy(),
|
|
806
|
+
log_identifier=log_id_prefix,
|
|
807
|
+
config=embed_eval_config,
|
|
808
|
+
)
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
if signals:
|
|
812
|
+
await self._handle_resolved_signals(
|
|
813
|
+
external_request_context,
|
|
814
|
+
signals,
|
|
815
|
+
original_rpc_id,
|
|
816
|
+
is_finalizing_context,
|
|
817
|
+
)
|
|
818
|
+
content_modified_or_signal_handled = True
|
|
819
|
+
|
|
820
|
+
if resolved_text is not None:
|
|
821
|
+
new_parts_for_owner.append(TextPart(text=resolved_text))
|
|
822
|
+
if is_streaming_status_update:
|
|
823
|
+
if resolved_text != text_to_resolve[:processed_idx]:
|
|
824
|
+
content_modified_or_signal_handled = True
|
|
825
|
+
elif resolved_text != original_part_text:
|
|
826
|
+
content_modified_or_signal_handled = True
|
|
827
|
+
|
|
828
|
+
if is_streaming_status_update:
|
|
829
|
+
current_buffer = text_to_resolve[processed_idx:]
|
|
830
|
+
elif (
|
|
831
|
+
processed_idx < len(text_to_resolve)
|
|
832
|
+
and not content_modified_or_signal_handled
|
|
833
|
+
):
|
|
834
|
+
log.warning(
|
|
835
|
+
"%s Unclosed embed in non-streaming TextPart. Remainder: '%s'",
|
|
836
|
+
log_id_prefix,
|
|
837
|
+
text_to_resolve[processed_idx:],
|
|
838
|
+
)
|
|
839
|
+
content_modified_or_signal_handled = True
|
|
840
|
+
|
|
841
|
+
elif (
|
|
842
|
+
isinstance(part_obj, FilePart)
|
|
843
|
+
and part_obj.file
|
|
844
|
+
and part_obj.file.bytes
|
|
845
|
+
):
|
|
846
|
+
mime_type = part_obj.file.mimeType or ""
|
|
847
|
+
is_container = is_text_based_mime_type(mime_type)
|
|
848
|
+
try:
|
|
849
|
+
decoded_content_for_check = base64.b64decode(
|
|
850
|
+
part_obj.file.bytes
|
|
851
|
+
).decode("utf-8", errors="ignore")
|
|
852
|
+
if (
|
|
853
|
+
is_container
|
|
854
|
+
and EMBED_DELIMITER_OPEN in decoded_content_for_check
|
|
855
|
+
):
|
|
856
|
+
original_content = decoded_content_for_check
|
|
857
|
+
resolved_content = (
|
|
858
|
+
await resolve_embeds_recursively_in_string(
|
|
859
|
+
text=original_content,
|
|
860
|
+
context=embed_eval_context,
|
|
861
|
+
resolver_func=evaluate_embed,
|
|
862
|
+
types_to_resolve=LATE_EMBED_TYPES,
|
|
863
|
+
log_identifier=log_id_prefix,
|
|
864
|
+
config=embed_eval_config,
|
|
865
|
+
max_depth=self.gateway_recursive_embed_depth,
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
if resolved_content != original_content:
|
|
869
|
+
new_file_content = part_obj.file.model_copy()
|
|
870
|
+
new_file_content.bytes = base64.b64encode(
|
|
871
|
+
resolved_content.encode("utf-8")
|
|
872
|
+
).decode("utf-8")
|
|
873
|
+
new_parts_for_owner.append(
|
|
874
|
+
FilePart(
|
|
875
|
+
file=new_file_content,
|
|
876
|
+
metadata=part_obj.metadata,
|
|
877
|
+
)
|
|
878
|
+
)
|
|
879
|
+
content_modified_or_signal_handled = True
|
|
880
|
+
else:
|
|
881
|
+
new_parts_for_owner.append(part_obj)
|
|
882
|
+
else:
|
|
883
|
+
new_parts_for_owner.append(part_obj)
|
|
884
|
+
except Exception as e:
|
|
885
|
+
log.warning(
|
|
886
|
+
"%s Error during recursive FilePart resolution for %s: %s. Using original.",
|
|
887
|
+
log_id_prefix,
|
|
888
|
+
part_obj.file.name,
|
|
889
|
+
e,
|
|
890
|
+
)
|
|
891
|
+
new_parts_for_owner.append(part_obj)
|
|
892
|
+
else:
|
|
893
|
+
new_parts_for_owner.append(part_obj)
|
|
894
|
+
|
|
895
|
+
parts_owner.parts = new_parts_for_owner
|
|
896
|
+
|
|
897
|
+
if is_streaming_status_update:
|
|
898
|
+
self.task_context_manager.store_context(
|
|
899
|
+
stream_buffer_key, current_buffer
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
return content_modified_or_signal_handled
|
|
903
|
+
|
|
904
|
+
async def _process_parsed_a2a_event(
|
|
905
|
+
self,
|
|
906
|
+
parsed_event: Union[
|
|
907
|
+
Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError
|
|
908
|
+
],
|
|
909
|
+
external_request_context: Dict[str, Any],
|
|
910
|
+
a2a_task_id: str,
|
|
911
|
+
original_rpc_id: Optional[str],
|
|
912
|
+
) -> None:
|
|
913
|
+
"""
|
|
914
|
+
Processes a parsed A2A event: resolves embeds, handles signals,
|
|
915
|
+
sends to external, and manages context.
|
|
916
|
+
"""
|
|
917
|
+
log_id_prefix = f"{self.log_identifier}[ProcessParsed:{a2a_task_id}]"
|
|
918
|
+
is_truly_final_event_for_context_cleanup = False
|
|
919
|
+
is_finalizing_context_for_embeds = False
|
|
920
|
+
|
|
921
|
+
if isinstance(parsed_event, JSONRPCError):
|
|
922
|
+
log.warning(
|
|
923
|
+
"%s Handling JSONRPCError for task %s.", log_id_prefix, a2a_task_id
|
|
924
|
+
)
|
|
925
|
+
await self._send_error_to_external(external_request_context, parsed_event)
|
|
926
|
+
is_truly_final_event_for_context_cleanup = True
|
|
927
|
+
else:
|
|
928
|
+
content_was_modified_or_signals_handled = False
|
|
929
|
+
|
|
930
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent) and parsed_event.final:
|
|
931
|
+
is_finalizing_context_for_embeds = True
|
|
932
|
+
elif isinstance(parsed_event, Task):
|
|
933
|
+
is_finalizing_context_for_embeds = True
|
|
934
|
+
|
|
935
|
+
if self.get_config("resolve_artifact_uris_in_gateway", False):
|
|
936
|
+
log.debug(
|
|
937
|
+
"%s Resolving artifact URIs before sending to external...",
|
|
938
|
+
log_id_prefix,
|
|
939
|
+
)
|
|
940
|
+
await self._resolve_uris_in_payload(parsed_event)
|
|
941
|
+
|
|
942
|
+
if not isinstance(parsed_event, JSONRPCError):
|
|
943
|
+
content_was_modified_or_signals_handled = (
|
|
944
|
+
await self._resolve_embeds_and_handle_signals(
|
|
945
|
+
parsed_event,
|
|
946
|
+
external_request_context,
|
|
947
|
+
a2a_task_id,
|
|
948
|
+
original_rpc_id,
|
|
949
|
+
is_finalizing_context=is_finalizing_context_for_embeds,
|
|
950
|
+
)
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
send_this_event_to_external = True
|
|
954
|
+
is_final_chunk_of_status_update = False
|
|
955
|
+
|
|
956
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent):
|
|
957
|
+
is_final_chunk_of_status_update = parsed_event.final
|
|
958
|
+
if (
|
|
959
|
+
not (
|
|
960
|
+
parsed_event.status
|
|
961
|
+
and parsed_event.status.message
|
|
962
|
+
and parsed_event.status.message.parts
|
|
963
|
+
)
|
|
964
|
+
and not parsed_event.metadata
|
|
965
|
+
and not is_final_chunk_of_status_update
|
|
966
|
+
and not content_was_modified_or_signals_handled
|
|
967
|
+
):
|
|
968
|
+
send_this_event_to_external = False
|
|
969
|
+
log.debug(
|
|
970
|
+
"%s Suppressing empty intermediate status update.",
|
|
971
|
+
log_id_prefix,
|
|
972
|
+
)
|
|
973
|
+
elif isinstance(parsed_event, TaskArtifactUpdateEvent):
|
|
974
|
+
if (
|
|
975
|
+
not (parsed_event.artifact and parsed_event.artifact.parts)
|
|
976
|
+
and not content_was_modified_or_signals_handled
|
|
977
|
+
):
|
|
978
|
+
send_this_event_to_external = False
|
|
979
|
+
log.debug("%s Suppressing empty artifact update.", log_id_prefix)
|
|
980
|
+
elif isinstance(parsed_event, Task):
|
|
981
|
+
is_truly_final_event_for_context_cleanup = True
|
|
982
|
+
|
|
983
|
+
if (
|
|
984
|
+
self._RESOLVE_EMBEDS_IN_FINAL_RESPONSE
|
|
985
|
+
and parsed_event.status
|
|
986
|
+
and parsed_event.status.message
|
|
987
|
+
and parsed_event.status.message.parts
|
|
988
|
+
):
|
|
989
|
+
log.debug(
|
|
990
|
+
"%s Resolving embeds in final task response...", log_id_prefix
|
|
991
|
+
)
|
|
992
|
+
combined_text = ""
|
|
993
|
+
non_text_parts = []
|
|
994
|
+
for part in parsed_event.status.message.parts:
|
|
995
|
+
if isinstance(part, TextPart) and part.text:
|
|
996
|
+
combined_text += part.text
|
|
997
|
+
else:
|
|
998
|
+
non_text_parts.append(part)
|
|
999
|
+
|
|
1000
|
+
if combined_text:
|
|
1001
|
+
embed_eval_context = {
|
|
1002
|
+
"artifact_service": self.shared_artifact_service,
|
|
1003
|
+
"session_context": {
|
|
1004
|
+
"app_name": external_request_context.get(
|
|
1005
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1006
|
+
),
|
|
1007
|
+
"user_id": external_request_context.get(
|
|
1008
|
+
"user_id_for_artifacts"
|
|
1009
|
+
),
|
|
1010
|
+
"session_id": external_request_context.get(
|
|
1011
|
+
"a2a_session_id"
|
|
1012
|
+
),
|
|
1013
|
+
},
|
|
1014
|
+
}
|
|
1015
|
+
embed_eval_config = {
|
|
1016
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
1017
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1018
|
+
}
|
|
1019
|
+
all_embed_types = EARLY_EMBED_TYPES.union(LATE_EMBED_TYPES)
|
|
1020
|
+
resolved_text, _, signals = await resolve_embeds_in_string(
|
|
1021
|
+
text=combined_text,
|
|
1022
|
+
context=embed_eval_context,
|
|
1023
|
+
resolver_func=evaluate_embed,
|
|
1024
|
+
types_to_resolve=all_embed_types,
|
|
1025
|
+
log_identifier=log_id_prefix,
|
|
1026
|
+
config=embed_eval_config,
|
|
1027
|
+
)
|
|
1028
|
+
if signals:
|
|
1029
|
+
log.debug(
|
|
1030
|
+
"%s Handling %d signals found during final response embed resolution.",
|
|
1031
|
+
log_id_prefix,
|
|
1032
|
+
len(signals),
|
|
1033
|
+
)
|
|
1034
|
+
await self._handle_resolved_signals(
|
|
1035
|
+
external_request_context,
|
|
1036
|
+
signals,
|
|
1037
|
+
original_rpc_id,
|
|
1038
|
+
is_finalizing_context=True,
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
new_parts = (
|
|
1042
|
+
[TextPart(text=resolved_text)] if resolved_text else []
|
|
1043
|
+
)
|
|
1044
|
+
new_parts.extend(non_text_parts)
|
|
1045
|
+
parsed_event.status.message.parts = new_parts
|
|
1046
|
+
log.info(
|
|
1047
|
+
"%s Final response text updated with resolved embeds.",
|
|
1048
|
+
log_id_prefix,
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
final_buffer_key = f"{a2a_task_id}_stream_buffer"
|
|
1052
|
+
remaining_buffer = self.task_context_manager.get_context(
|
|
1053
|
+
final_buffer_key
|
|
1054
|
+
)
|
|
1055
|
+
if remaining_buffer:
|
|
1056
|
+
log.info(
|
|
1057
|
+
"%s Flushing remaining buffer for task %s before final response.",
|
|
1058
|
+
log_id_prefix,
|
|
1059
|
+
a2a_task_id,
|
|
1060
|
+
)
|
|
1061
|
+
embed_eval_context = {
|
|
1062
|
+
"artifact_service": self.shared_artifact_service,
|
|
1063
|
+
"session_context": {
|
|
1064
|
+
"app_name": external_request_context.get(
|
|
1065
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1066
|
+
),
|
|
1067
|
+
"user_id": external_request_context.get(
|
|
1068
|
+
"user_id_for_artifacts"
|
|
1069
|
+
),
|
|
1070
|
+
"session_id": external_request_context.get(
|
|
1071
|
+
"a2a_session_id"
|
|
1072
|
+
),
|
|
1073
|
+
},
|
|
1074
|
+
}
|
|
1075
|
+
embed_eval_config = {
|
|
1076
|
+
"gateway_artifact_content_limit_bytes": self.gateway_artifact_content_limit_bytes,
|
|
1077
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1078
|
+
}
|
|
1079
|
+
resolved_remaining_text, _, signals = (
|
|
1080
|
+
await resolve_embeds_in_string(
|
|
1081
|
+
remaining_buffer,
|
|
1082
|
+
embed_eval_context,
|
|
1083
|
+
evaluate_embed,
|
|
1084
|
+
LATE_EMBED_TYPES.copy(),
|
|
1085
|
+
log_id_prefix,
|
|
1086
|
+
embed_eval_config,
|
|
1087
|
+
)
|
|
1088
|
+
)
|
|
1089
|
+
await self._handle_resolved_signals(
|
|
1090
|
+
external_request_context,
|
|
1091
|
+
signals,
|
|
1092
|
+
original_rpc_id,
|
|
1093
|
+
is_finalizing_context=True,
|
|
1094
|
+
)
|
|
1095
|
+
if resolved_remaining_text:
|
|
1096
|
+
flush_status = TaskStatus(
|
|
1097
|
+
state=TaskState.WORKING,
|
|
1098
|
+
message=A2AMessage(
|
|
1099
|
+
role="agent",
|
|
1100
|
+
parts=[TextPart(text=resolved_remaining_text)],
|
|
1101
|
+
),
|
|
1102
|
+
)
|
|
1103
|
+
flush_event = TaskStatusUpdateEvent(
|
|
1104
|
+
id=a2a_task_id,
|
|
1105
|
+
status=flush_status,
|
|
1106
|
+
final=False,
|
|
1107
|
+
)
|
|
1108
|
+
await self._send_update_to_external(
|
|
1109
|
+
external_request_context, flush_event, True
|
|
1110
|
+
)
|
|
1111
|
+
self.task_context_manager.remove_context(final_buffer_key)
|
|
1112
|
+
|
|
1113
|
+
if send_this_event_to_external:
|
|
1114
|
+
if isinstance(parsed_event, Task):
|
|
1115
|
+
await self._send_final_response_to_external(
|
|
1116
|
+
external_request_context, parsed_event
|
|
1117
|
+
)
|
|
1118
|
+
elif isinstance(
|
|
1119
|
+
parsed_event, (TaskStatusUpdateEvent, TaskArtifactUpdateEvent)
|
|
1120
|
+
):
|
|
1121
|
+
final_chunk_flag = (
|
|
1122
|
+
is_final_chunk_of_status_update
|
|
1123
|
+
if isinstance(parsed_event, TaskStatusUpdateEvent)
|
|
1124
|
+
else False
|
|
1125
|
+
)
|
|
1126
|
+
await self._send_update_to_external(
|
|
1127
|
+
external_request_context, parsed_event, final_chunk_flag
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
if is_truly_final_event_for_context_cleanup:
|
|
1131
|
+
log.info(
|
|
1132
|
+
"%s Truly final event processed for task %s. Removing context.",
|
|
1133
|
+
log_id_prefix,
|
|
1134
|
+
a2a_task_id,
|
|
1135
|
+
)
|
|
1136
|
+
self.task_context_manager.remove_context(a2a_task_id)
|
|
1137
|
+
self.task_context_manager.remove_context(f"{a2a_task_id}_stream_buffer")
|
|
1138
|
+
|
|
1139
|
+
async def _handle_agent_event(
|
|
1140
|
+
self, topic: str, payload: Dict, task_id_from_topic: str
|
|
1141
|
+
) -> bool:
|
|
1142
|
+
"""
|
|
1143
|
+
Handles messages received on gateway response and status topics.
|
|
1144
|
+
Parses the payload, retrieves context using task_id_from_topic, and dispatches for processing.
|
|
1145
|
+
"""
|
|
1146
|
+
try:
|
|
1147
|
+
rpc_response = JSONRPCResponse(**payload)
|
|
1148
|
+
except Exception as e:
|
|
1149
|
+
log.error(
|
|
1150
|
+
"%s Failed to parse payload as JSONRPCResponse for topic %s (Task ID from topic: %s): %s. Payload: %s",
|
|
1151
|
+
self.log_identifier,
|
|
1152
|
+
topic,
|
|
1153
|
+
task_id_from_topic,
|
|
1154
|
+
e,
|
|
1155
|
+
payload,
|
|
1156
|
+
)
|
|
1157
|
+
return False
|
|
1158
|
+
|
|
1159
|
+
original_rpc_id = str(rpc_response.id)
|
|
1160
|
+
|
|
1161
|
+
external_request_context = self.task_context_manager.get_context(
|
|
1162
|
+
task_id_from_topic
|
|
1163
|
+
)
|
|
1164
|
+
if not external_request_context:
|
|
1165
|
+
log.warning(
|
|
1166
|
+
"%s No external context found for A2A Task ID: %s (from topic). Ignoring message. Topic: %s, RPC ID: %s",
|
|
1167
|
+
self.log_identifier,
|
|
1168
|
+
task_id_from_topic,
|
|
1169
|
+
topic,
|
|
1170
|
+
original_rpc_id,
|
|
1171
|
+
)
|
|
1172
|
+
return True
|
|
1173
|
+
|
|
1174
|
+
external_request_context["a2a_task_id_for_event"] = task_id_from_topic
|
|
1175
|
+
external_request_context["original_rpc_id"] = original_rpc_id
|
|
1176
|
+
|
|
1177
|
+
parsed_event_obj: Union[
|
|
1178
|
+
Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, JSONRPCError, None
|
|
1179
|
+
] = None
|
|
1180
|
+
if rpc_response.error:
|
|
1181
|
+
parsed_event_obj = rpc_response.error
|
|
1182
|
+
elif rpc_response.result:
|
|
1183
|
+
parsed_event_obj = self._parse_a2a_event_from_rpc_result(
|
|
1184
|
+
rpc_response.result, task_id_from_topic
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
if not parsed_event_obj:
|
|
1188
|
+
log.error(
|
|
1189
|
+
"%s Failed to parse or validate A2A event from RPC result for task %s. Result: %s",
|
|
1190
|
+
self.log_identifier,
|
|
1191
|
+
task_id_from_topic,
|
|
1192
|
+
rpc_response.result,
|
|
1193
|
+
)
|
|
1194
|
+
generic_error = JSONRPCError(
|
|
1195
|
+
code=-32000, message="Invalid event structure received from agent."
|
|
1196
|
+
)
|
|
1197
|
+
await self._send_error_to_external(external_request_context, generic_error)
|
|
1198
|
+
self.task_context_manager.remove_context(task_id_from_topic)
|
|
1199
|
+
self.task_context_manager.remove_context(
|
|
1200
|
+
f"{task_id_from_topic}_stream_buffer"
|
|
1201
|
+
)
|
|
1202
|
+
return False
|
|
1203
|
+
|
|
1204
|
+
try:
|
|
1205
|
+
await self._process_parsed_a2a_event(
|
|
1206
|
+
parsed_event_obj,
|
|
1207
|
+
external_request_context,
|
|
1208
|
+
task_id_from_topic,
|
|
1209
|
+
original_rpc_id,
|
|
1210
|
+
)
|
|
1211
|
+
return True
|
|
1212
|
+
except Exception as e:
|
|
1213
|
+
log.exception(
|
|
1214
|
+
"%s Error in _process_parsed_a2a_event for task %s: %s",
|
|
1215
|
+
self.log_identifier,
|
|
1216
|
+
task_id_from_topic,
|
|
1217
|
+
e,
|
|
1218
|
+
)
|
|
1219
|
+
error_obj = JSONRPCError(
|
|
1220
|
+
code=-32000, message=f"Gateway processing error: {e}"
|
|
1221
|
+
)
|
|
1222
|
+
await self._send_error_to_external(external_request_context, error_obj)
|
|
1223
|
+
self.task_context_manager.remove_context(task_id_from_topic)
|
|
1224
|
+
self.task_context_manager.remove_context(
|
|
1225
|
+
f"{task_id_from_topic}_stream_buffer"
|
|
1226
|
+
)
|
|
1227
|
+
return False
|
|
1228
|
+
|
|
1229
|
+
async def _message_processor_loop(self):
|
|
1230
|
+
log.info("%s Starting message processor loop...", self.log_identifier)
|
|
1231
|
+
loop = asyncio.get_running_loop()
|
|
1232
|
+
|
|
1233
|
+
while not self.stop_signal.is_set():
|
|
1234
|
+
original_broker_message: Optional[SolaceMessage] = None
|
|
1235
|
+
item = None
|
|
1236
|
+
processed_successfully = False
|
|
1237
|
+
topic = None
|
|
1238
|
+
|
|
1239
|
+
try:
|
|
1240
|
+
item = await loop.run_in_executor(None, self.internal_event_queue.get)
|
|
1241
|
+
|
|
1242
|
+
if item is None:
|
|
1243
|
+
log.info(
|
|
1244
|
+
"%s Received shutdown sentinel. Exiting message processor loop.",
|
|
1245
|
+
self.log_identifier,
|
|
1246
|
+
)
|
|
1247
|
+
break
|
|
1248
|
+
|
|
1249
|
+
topic = item.get("topic")
|
|
1250
|
+
payload = item.get("payload")
|
|
1251
|
+
original_broker_message = item.get("_original_broker_message")
|
|
1252
|
+
|
|
1253
|
+
if not topic or payload is None or not original_broker_message:
|
|
1254
|
+
log.warning(
|
|
1255
|
+
"%s Invalid item received from internal queue: %s",
|
|
1256
|
+
self.log_identifier,
|
|
1257
|
+
item,
|
|
1258
|
+
)
|
|
1259
|
+
processed_successfully = False
|
|
1260
|
+
continue
|
|
1261
|
+
|
|
1262
|
+
if _topic_matches_subscription(
|
|
1263
|
+
topic, get_discovery_topic(self.namespace)
|
|
1264
|
+
):
|
|
1265
|
+
processed_successfully = await self._handle_discovery_message(
|
|
1266
|
+
payload
|
|
1267
|
+
)
|
|
1268
|
+
elif _topic_matches_subscription(
|
|
1269
|
+
topic,
|
|
1270
|
+
get_gateway_response_subscription_topic(
|
|
1271
|
+
self.namespace, self.gateway_id
|
|
1272
|
+
),
|
|
1273
|
+
) or _topic_matches_subscription(
|
|
1274
|
+
topic,
|
|
1275
|
+
get_gateway_status_subscription_topic(
|
|
1276
|
+
self.namespace, self.gateway_id
|
|
1277
|
+
),
|
|
1278
|
+
):
|
|
1279
|
+
task_id_from_topic: Optional[str] = None
|
|
1280
|
+
response_sub = get_gateway_response_subscription_topic(
|
|
1281
|
+
self.namespace, self.gateway_id
|
|
1282
|
+
)
|
|
1283
|
+
status_sub = get_gateway_status_subscription_topic(
|
|
1284
|
+
self.namespace, self.gateway_id
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
if _topic_matches_subscription(topic, response_sub):
|
|
1288
|
+
task_id_from_topic = self._extract_task_id_from_topic(
|
|
1289
|
+
topic, response_sub
|
|
1290
|
+
)
|
|
1291
|
+
elif _topic_matches_subscription(topic, status_sub):
|
|
1292
|
+
task_id_from_topic = self._extract_task_id_from_topic(
|
|
1293
|
+
topic, status_sub
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
if task_id_from_topic:
|
|
1297
|
+
processed_successfully = await self._handle_agent_event(
|
|
1298
|
+
topic, payload, task_id_from_topic
|
|
1299
|
+
)
|
|
1300
|
+
else:
|
|
1301
|
+
log.error(
|
|
1302
|
+
"%s Could not extract task_id from topic %s for _handle_agent_event. Ignoring.",
|
|
1303
|
+
self.log_identifier,
|
|
1304
|
+
topic,
|
|
1305
|
+
)
|
|
1306
|
+
processed_successfully = False
|
|
1307
|
+
else:
|
|
1308
|
+
log.warning(
|
|
1309
|
+
"%s Received message on unhandled topic: %s. Acknowledging.",
|
|
1310
|
+
self.log_identifier,
|
|
1311
|
+
topic,
|
|
1312
|
+
)
|
|
1313
|
+
processed_successfully = True
|
|
1314
|
+
|
|
1315
|
+
except queue.Empty:
|
|
1316
|
+
continue
|
|
1317
|
+
except asyncio.CancelledError:
|
|
1318
|
+
log.info("%s Message processor loop cancelled.", self.log_identifier)
|
|
1319
|
+
break
|
|
1320
|
+
except Exception as e:
|
|
1321
|
+
log.exception(
|
|
1322
|
+
"%s Unhandled error in message processor loop: %s",
|
|
1323
|
+
self.log_identifier,
|
|
1324
|
+
e,
|
|
1325
|
+
)
|
|
1326
|
+
processed_successfully = False
|
|
1327
|
+
await asyncio.sleep(1)
|
|
1328
|
+
finally:
|
|
1329
|
+
if original_broker_message:
|
|
1330
|
+
if processed_successfully:
|
|
1331
|
+
original_broker_message.call_acknowledgements()
|
|
1332
|
+
else:
|
|
1333
|
+
original_broker_message.call_negative_acknowledgements()
|
|
1334
|
+
log.warning(
|
|
1335
|
+
"%s NACKed SolaceMessage for topic: %s",
|
|
1336
|
+
self.log_identifier,
|
|
1337
|
+
topic or "unknown",
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
if item and item is not None:
|
|
1341
|
+
self.internal_event_queue.task_done()
|
|
1342
|
+
|
|
1343
|
+
log.info("%s Message processor loop finished.", self.log_identifier)
|
|
1344
|
+
|
|
1345
|
+
def _run_async_operations(self):
|
|
1346
|
+
log.info(
|
|
1347
|
+
"%s Initializing asyncio event loop in dedicated thread...",
|
|
1348
|
+
self.log_identifier,
|
|
1349
|
+
)
|
|
1350
|
+
self.async_loop = asyncio.new_event_loop()
|
|
1351
|
+
asyncio.set_event_loop(self.async_loop)
|
|
1352
|
+
|
|
1353
|
+
processor_task = None
|
|
1354
|
+
try:
|
|
1355
|
+
log.info(
|
|
1356
|
+
"%s Starting _message_processor_loop as an asyncio task.",
|
|
1357
|
+
self.log_identifier,
|
|
1358
|
+
)
|
|
1359
|
+
processor_task = self.async_loop.create_task(self._message_processor_loop())
|
|
1360
|
+
|
|
1361
|
+
log.info(
|
|
1362
|
+
"%s Calling _start_listener() to initiate external platform connection.",
|
|
1363
|
+
self.log_identifier,
|
|
1364
|
+
)
|
|
1365
|
+
self._start_listener()
|
|
1366
|
+
|
|
1367
|
+
log.info(
|
|
1368
|
+
"%s Running asyncio event loop forever (or until stop_signal).",
|
|
1369
|
+
self.log_identifier,
|
|
1370
|
+
)
|
|
1371
|
+
self.async_loop.run_forever()
|
|
1372
|
+
|
|
1373
|
+
except Exception as e:
|
|
1374
|
+
log.exception(
|
|
1375
|
+
"%s Unhandled exception in _run_async_operations: %s",
|
|
1376
|
+
self.log_identifier,
|
|
1377
|
+
e,
|
|
1378
|
+
)
|
|
1379
|
+
self.stop_signal.set()
|
|
1380
|
+
finally:
|
|
1381
|
+
if processor_task and not processor_task.done():
|
|
1382
|
+
log.info(
|
|
1383
|
+
"%s Cancelling _message_processor_loop task.", self.log_identifier
|
|
1384
|
+
)
|
|
1385
|
+
processor_task.cancel()
|
|
1386
|
+
try:
|
|
1387
|
+
self.async_loop.run_until_complete(
|
|
1388
|
+
asyncio.gather(processor_task, return_exceptions=True)
|
|
1389
|
+
)
|
|
1390
|
+
except RuntimeError as loop_err:
|
|
1391
|
+
log.warning(
|
|
1392
|
+
"%s Error awaiting processor task during cleanup (loop closed?): %s",
|
|
1393
|
+
self.log_identifier,
|
|
1394
|
+
loop_err,
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
if self.async_loop.is_running():
|
|
1398
|
+
log.info(
|
|
1399
|
+
"%s Stopping asyncio event loop from _run_async_operations finally block.",
|
|
1400
|
+
self.log_identifier,
|
|
1401
|
+
)
|
|
1402
|
+
self.async_loop.stop()
|
|
1403
|
+
log.info(
|
|
1404
|
+
"%s Async operations loop finished in dedicated thread.",
|
|
1405
|
+
self.log_identifier,
|
|
1406
|
+
)
|
|
1407
|
+
|
|
1408
|
+
def run(self):
|
|
1409
|
+
log.info("%s Starting BaseGatewayComponent run method.", self.log_identifier)
|
|
1410
|
+
if not self.async_thread or not self.async_thread.is_alive():
|
|
1411
|
+
self.async_thread = threading.Thread(
|
|
1412
|
+
target=self._run_async_operations,
|
|
1413
|
+
name=f"{self.name}_AsyncOpsThread",
|
|
1414
|
+
daemon=True,
|
|
1415
|
+
)
|
|
1416
|
+
self.async_thread.start()
|
|
1417
|
+
log.info("%s Async operations thread started.", self.log_identifier)
|
|
1418
|
+
else:
|
|
1419
|
+
log.warning(
|
|
1420
|
+
"%s Async operations thread already running.", self.log_identifier
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
super().run()
|
|
1424
|
+
log.info("%s BaseGatewayComponent run method finished.", self.log_identifier)
|
|
1425
|
+
|
|
1426
|
+
def cleanup(self):
|
|
1427
|
+
log.info("%s Starting cleanup for BaseGatewayComponent...", self.log_identifier)
|
|
1428
|
+
|
|
1429
|
+
log.info("%s Calling _stop_listener()...", self.log_identifier)
|
|
1430
|
+
try:
|
|
1431
|
+
if (
|
|
1432
|
+
self.async_loop
|
|
1433
|
+
and not self.async_loop.is_running()
|
|
1434
|
+
and self.async_thread
|
|
1435
|
+
and self.async_thread.is_alive()
|
|
1436
|
+
):
|
|
1437
|
+
log.warning(
|
|
1438
|
+
"%s Async loop not running during cleanup, _stop_listener might face issues if it needs the loop.",
|
|
1439
|
+
self.log_identifier,
|
|
1440
|
+
)
|
|
1441
|
+
self._stop_listener()
|
|
1442
|
+
except Exception as e:
|
|
1443
|
+
log.exception(
|
|
1444
|
+
"%s Error during _stop_listener(): %s", self.log_identifier, e
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
if self.internal_event_queue:
|
|
1448
|
+
log.info(
|
|
1449
|
+
"%s Signaling _message_processor_loop to stop...", self.log_identifier
|
|
1450
|
+
)
|
|
1451
|
+
self.internal_event_queue.put(None)
|
|
1452
|
+
|
|
1453
|
+
if self.async_loop and self.async_loop.is_running():
|
|
1454
|
+
log.info("%s Requesting asyncio loop to stop...", self.log_identifier)
|
|
1455
|
+
self.async_loop.call_soon_threadsafe(self.async_loop.stop)
|
|
1456
|
+
|
|
1457
|
+
if self.async_thread and self.async_thread.is_alive():
|
|
1458
|
+
log.info(
|
|
1459
|
+
"%s Joining async operations thread (timeout 10s)...",
|
|
1460
|
+
self.log_identifier,
|
|
1461
|
+
)
|
|
1462
|
+
self.async_thread.join(timeout=10)
|
|
1463
|
+
if self.async_thread.is_alive():
|
|
1464
|
+
log.warning(
|
|
1465
|
+
"%s Async operations thread did not join cleanly.",
|
|
1466
|
+
self.log_identifier,
|
|
1467
|
+
)
|
|
1468
|
+
|
|
1469
|
+
if self.async_loop and not self.async_loop.is_closed():
|
|
1470
|
+
if self.async_loop.is_running():
|
|
1471
|
+
self.async_loop.call_soon_threadsafe(self.async_loop.stop)
|
|
1472
|
+
log.info(
|
|
1473
|
+
"%s Closing asyncio event loop (if not already closed by its thread).",
|
|
1474
|
+
self.log_identifier,
|
|
1475
|
+
)
|
|
1476
|
+
if not self.async_loop.is_running():
|
|
1477
|
+
self.async_loop.close()
|
|
1478
|
+
else:
|
|
1479
|
+
self.async_loop.call_soon_threadsafe(self.async_loop.close)
|
|
1480
|
+
|
|
1481
|
+
super().cleanup()
|
|
1482
|
+
log.info("%s BaseGatewayComponent cleanup finished.", self.log_identifier)
|
|
1483
|
+
|
|
1484
|
+
@abstractmethod
|
|
1485
|
+
async def _extract_initial_claims(
|
|
1486
|
+
self, external_event_data: Any
|
|
1487
|
+
) -> Optional[Dict[str, Any]]:
|
|
1488
|
+
"""
|
|
1489
|
+
Extracts the primary identity claims from a platform-specific event.
|
|
1490
|
+
This method MUST be implemented by derived gateway components.
|
|
1491
|
+
|
|
1492
|
+
Args:
|
|
1493
|
+
external_event_data: Raw event data from the external platform
|
|
1494
|
+
(e.g., FastAPIRequest, Slack event dictionary).
|
|
1495
|
+
|
|
1496
|
+
Returns:
|
|
1497
|
+
A dictionary of initial claims, which MUST include an 'id' key.
|
|
1498
|
+
Example: {"id": "user@example.com", "source": "slack_api"}
|
|
1499
|
+
Return None if authentication fails.
|
|
1500
|
+
"""
|
|
1501
|
+
pass
|
|
1502
|
+
|
|
1503
|
+
@abstractmethod
|
|
1504
|
+
def _start_listener(self) -> None:
|
|
1505
|
+
pass
|
|
1506
|
+
|
|
1507
|
+
@abstractmethod
|
|
1508
|
+
def _stop_listener(self) -> None:
|
|
1509
|
+
pass
|
|
1510
|
+
|
|
1511
|
+
@abstractmethod
|
|
1512
|
+
def _translate_external_input(
|
|
1513
|
+
self, external_event: Any
|
|
1514
|
+
) -> Tuple[str, List[A2APart], Dict[str, Any]]:
|
|
1515
|
+
pass
|
|
1516
|
+
|
|
1517
|
+
@abstractmethod
|
|
1518
|
+
async def _send_update_to_external(
|
|
1519
|
+
self,
|
|
1520
|
+
external_request_context: Dict[str, Any],
|
|
1521
|
+
event_data: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent],
|
|
1522
|
+
is_final_chunk_of_update: bool,
|
|
1523
|
+
) -> None:
|
|
1524
|
+
pass
|
|
1525
|
+
|
|
1526
|
+
@abstractmethod
|
|
1527
|
+
async def _send_final_response_to_external(
|
|
1528
|
+
self, external_request_context: Dict[str, Any], task_data: Task
|
|
1529
|
+
) -> None:
|
|
1530
|
+
pass
|
|
1531
|
+
|
|
1532
|
+
@abstractmethod
|
|
1533
|
+
async def _send_error_to_external(
|
|
1534
|
+
self, external_request_context: Dict[str, Any], error_data: JSONRPCError
|
|
1535
|
+
) -> None:
|
|
1536
|
+
pass
|
|
1537
|
+
|
|
1538
|
+
def invoke(self, message, data):
|
|
1539
|
+
if isinstance(message, SolaceMessage):
|
|
1540
|
+
message.call_acknowledgements()
|
|
1541
|
+
log.warning("%s Invoke method called unexpectedly.", self.log_identifier)
|
|
1542
|
+
return None
|