solace-agent-mesh 1.6.1__py3-none-any.whl → 1.13.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solace-agent-mesh might be problematic. Click here for more details.
- solace_agent_mesh/agent/adk/alembic/README +74 -0
- solace_agent_mesh/agent/adk/alembic/env.py +77 -0
- solace_agent_mesh/agent/adk/alembic/script.py.mako +28 -0
- solace_agent_mesh/agent/adk/alembic/versions/e2902798564d_adk_session_db_upgrade.py +52 -0
- solace_agent_mesh/agent/adk/alembic.ini +112 -0
- solace_agent_mesh/agent/adk/app_llm_agent.py +26 -0
- solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +165 -1
- solace_agent_mesh/agent/adk/artifacts/s3_artifact_service.py +163 -0
- solace_agent_mesh/agent/adk/callbacks.py +852 -109
- solace_agent_mesh/agent/adk/embed_resolving_mcp_toolset.py +234 -36
- solace_agent_mesh/agent/adk/intelligent_mcp_callbacks.py +52 -5
- solace_agent_mesh/agent/adk/mcp_content_processor.py +1 -1
- solace_agent_mesh/agent/adk/models/lite_llm.py +77 -21
- solace_agent_mesh/agent/adk/models/oauth2_token_manager.py +24 -137
- solace_agent_mesh/agent/adk/runner.py +85 -20
- solace_agent_mesh/agent/adk/schema_migration.py +88 -0
- solace_agent_mesh/agent/adk/services.py +94 -18
- solace_agent_mesh/agent/adk/setup.py +281 -65
- solace_agent_mesh/agent/adk/stream_parser.py +231 -37
- solace_agent_mesh/agent/adk/tool_wrapper.py +3 -0
- solace_agent_mesh/agent/protocol/event_handlers.py +472 -137
- solace_agent_mesh/agent/proxies/a2a/app.py +3 -2
- solace_agent_mesh/agent/proxies/a2a/component.py +572 -75
- solace_agent_mesh/agent/proxies/a2a/config.py +80 -4
- solace_agent_mesh/agent/proxies/base/app.py +3 -2
- solace_agent_mesh/agent/proxies/base/component.py +188 -22
- solace_agent_mesh/agent/proxies/base/proxy_task_context.py +3 -1
- solace_agent_mesh/agent/sac/app.py +91 -3
- solace_agent_mesh/agent/sac/component.py +591 -157
- solace_agent_mesh/agent/sac/patch_adk.py +8 -16
- solace_agent_mesh/agent/sac/task_execution_context.py +146 -4
- solace_agent_mesh/agent/tools/__init__.py +3 -0
- solace_agent_mesh/agent/tools/audio_tools.py +3 -3
- solace_agent_mesh/agent/tools/builtin_artifact_tools.py +710 -171
- solace_agent_mesh/agent/tools/deep_research_tools.py +2161 -0
- solace_agent_mesh/agent/tools/dynamic_tool.py +2 -0
- solace_agent_mesh/agent/tools/peer_agent_tool.py +82 -15
- solace_agent_mesh/agent/tools/time_tools.py +126 -0
- solace_agent_mesh/agent/tools/tool_config_types.py +57 -2
- solace_agent_mesh/agent/tools/web_search_tools.py +279 -0
- solace_agent_mesh/agent/tools/web_tools.py +125 -17
- solace_agent_mesh/agent/utils/artifact_helpers.py +248 -6
- solace_agent_mesh/agent/utils/context_helpers.py +17 -0
- solace_agent_mesh/assets/docs/404.html +6 -6
- solace_agent_mesh/assets/docs/assets/css/{styles.906a1503.css → styles.8162edfb.css} +1 -1
- solace_agent_mesh/assets/docs/assets/js/05749d90.19ac4f35.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.e186750d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/15e40e79.434bb30f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/17896441.e612dfb4.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2279.550aa580.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/{17896441.a5e82f9b.js.LICENSE.txt → 2279.550aa580.js.LICENSE.txt} +6 -0
- solace_agent_mesh/assets/docs/assets/js/240a0364.83e37aa8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2987107d.a80604f9.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/2e32b5e0.2f0db237.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3a6c6137.7e61915d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.7f7ab1c1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.e53c9b78.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/41adc471.0e95b87c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/4667dc50.bf2ad456.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/49eed117.493d6f99.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/{509e993c.4c7a1a6d.js → 509e993c.a1fbf45a.js} +1 -1
- solace_agent_mesh/assets/docs/assets/js/547e15cc.8e6da617.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/55b7b518.29d6e75d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5b8d9c11.d4eb37b8.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.1ee87753.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/60702c0e.a8bdd79b.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/631738c7.fa471607.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/64195356.09dbd087.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/66d4869e.30340bd3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6a520c9d.b6e3f2ce.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6aaedf65.7253541d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.a5b36a60.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/6d84eae0.fd23ba4a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/71da7b71.374b9d54.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/729898df.7249e9fd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/7e294c01.7c5f6906.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8024126c.e3467286.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/81a99df0.7ed65d45.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/82fbfb93.161823a5.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/8b032486.91a91afc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.975e428a.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/94e8668d.16083b3f.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/9bb13469.4523ae20.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a7d42657.a956689d.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/a94703ab.3e5fbcb3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ab9708a8.3e563275.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ad87452a.9d73dad6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/c93cbaa0.0e0d8baf.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cab03b5b.6a073091.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/cbe2e9ea.07e170dd.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.b62f7b08.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.c37a755e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.b682e9c2.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/de915948.44a432bc.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e04b235d.06d23db6.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e1b6eeb4.deb2b62e.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.1476f570.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.acc800d3.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/e92d0134.c147a429.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ee0c2fe7.94d0a351.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/f284c35a.cc97854c.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.74710fc1.js +1 -0
- solace_agent_mesh/assets/docs/assets/js/main.d634009f.js +2 -0
- solace_agent_mesh/assets/docs/assets/js/runtime~main.27bb82a7.js +1 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/agents/index.html +68 -68
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/artifact-management/index.html +50 -50
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/audio-tools/index.html +42 -42
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/data-analysis-tools/index.html +55 -55
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/embeds/index.html +82 -68
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/image-tools/index.html +81 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/index.html +67 -50
- solace_agent_mesh/assets/docs/docs/documentation/components/builtin-tools/research-tools/index.html +136 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/cli/index.html +178 -144
- solace_agent_mesh/assets/docs/docs/documentation/components/gateways/index.html +43 -42
- solace_agent_mesh/assets/docs/docs/documentation/components/index.html +20 -18
- solace_agent_mesh/assets/docs/docs/documentation/components/orchestrator/index.html +23 -23
- solace_agent_mesh/assets/docs/docs/documentation/components/platform-service/index.html +33 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/plugins/index.html +45 -45
- solace_agent_mesh/assets/docs/docs/documentation/components/projects/index.html +182 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/prompts/index.html +147 -0
- solace_agent_mesh/assets/docs/docs/documentation/components/proxies/index.html +208 -125
- solace_agent_mesh/assets/docs/docs/documentation/components/speech/index.html +52 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/debugging/index.html +28 -49
- solace_agent_mesh/assets/docs/docs/documentation/deploying/deployment-options/index.html +29 -30
- solace_agent_mesh/assets/docs/docs/documentation/deploying/index.html +14 -14
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/index.html +47 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/kubernetes/kubernetes-deployment-guide/index.html +197 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/logging/index.html +90 -0
- solace_agent_mesh/assets/docs/docs/documentation/deploying/observability/index.html +17 -16
- solace_agent_mesh/assets/docs/docs/documentation/deploying/proxy_configuration/index.html +49 -0
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-agents/index.html +38 -38
- solace_agent_mesh/assets/docs/docs/documentation/developing/create-gateways/index.html +162 -171
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-python-tools/index.html +67 -49
- solace_agent_mesh/assets/docs/docs/documentation/developing/creating-service-providers/index.html +17 -17
- solace_agent_mesh/assets/docs/docs/documentation/developing/evaluations/index.html +51 -51
- solace_agent_mesh/assets/docs/docs/documentation/developing/index.html +22 -22
- solace_agent_mesh/assets/docs/docs/documentation/developing/structure/index.html +27 -27
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/bedrock-agents/index.html +135 -135
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/custom-agent/index.html +66 -66
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/event-mesh-gateway/index.html +51 -51
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mcp-integration/index.html +50 -38
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/mongodb-integration/index.html +86 -86
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rag-integration/index.html +51 -51
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/rest-gateway/index.html +24 -24
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/slack-integration/index.html +30 -30
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/sql-database/index.html +44 -44
- solace_agent_mesh/assets/docs/docs/documentation/developing/tutorials/teams-integration/index.html +115 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/agent-builder/index.html +86 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/connectors/index.html +67 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/index.html +23 -19
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/installation/index.html +40 -37
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/openapi-tools/index.html +324 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/rbac-setup-guide/index.html +112 -87
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/secure-user-delegated-access/index.html +440 -0
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/single-sign-on/index.html +87 -64
- solace_agent_mesh/assets/docs/docs/documentation/enterprise/wheel-installation/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/architecture/index.html +44 -44
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/index.html +39 -37
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +30 -30
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/try-agent-mesh/index.html +18 -18
- solace_agent_mesh/assets/docs/docs/documentation/getting-started/vibe_coding/index.html +62 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/artifact-storage/index.html +311 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/configurations/index.html +39 -42
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/index.html +14 -14
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/installation/index.html +27 -25
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/large_language_models/index.html +69 -69
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/run-project/index.html +72 -72
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/session-storage/index.html +251 -0
- solace_agent_mesh/assets/docs/docs/documentation/installing-and-configuring/user-feedback/index.html +88 -0
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-gateway-upgrade-to-0.3.0/index.html +42 -42
- solace_agent_mesh/assets/docs/docs/documentation/migrations/a2a-upgrade/a2a-technical-migration-map/index.html +20 -20
- solace_agent_mesh/assets/docs/docs/documentation/migrations/platform-service-split/index.html +85 -0
- solace_agent_mesh/assets/docs/lunr-index-1768329217460.json +1 -0
- solace_agent_mesh/assets/docs/lunr-index.json +1 -1
- solace_agent_mesh/assets/docs/search-doc-1768329217460.json +1 -0
- solace_agent_mesh/assets/docs/search-doc.json +1 -1
- solace_agent_mesh/assets/docs/sitemap.xml +1 -1
- solace_agent_mesh/cli/__init__.py +1 -1
- solace_agent_mesh/cli/commands/add_cmd/__init__.py +3 -1
- solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +6 -1
- solace_agent_mesh/cli/commands/add_cmd/proxy_cmd.py +100 -0
- solace_agent_mesh/cli/commands/docs_cmd.py +4 -1
- solace_agent_mesh/cli/commands/eval_cmd.py +1 -1
- solace_agent_mesh/cli/commands/init_cmd/__init__.py +15 -0
- solace_agent_mesh/cli/commands/init_cmd/directory_step.py +1 -1
- solace_agent_mesh/cli/commands/init_cmd/env_step.py +30 -3
- solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +3 -4
- solace_agent_mesh/cli/commands/init_cmd/platform_service_step.py +85 -0
- solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +16 -3
- solace_agent_mesh/cli/commands/plugin_cmd/add_cmd.py +2 -1
- solace_agent_mesh/cli/commands/plugin_cmd/catalog_cmd.py +1 -0
- solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +3 -3
- solace_agent_mesh/cli/commands/run_cmd.py +64 -49
- solace_agent_mesh/cli/commands/tools_cmd.py +315 -0
- solace_agent_mesh/cli/main.py +15 -0
- solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-BTf6dqwp.js → authCallback-KnKMP_vb.js} +1 -1
- solace_agent_mesh/client/webui/frontend/static/assets/client-DpBL2stg.js +25 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-Cd498TV2.js +435 -0
- solace_agent_mesh/client/webui/frontend/static/assets/main-rSf8Vu29.css +1 -0
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-CGk8Suyh.js +565 -0
- solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
- solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
- solace_agent_mesh/client/webui/frontend/static/mockServiceWorker.js +336 -0
- solace_agent_mesh/client/webui/frontend/static/ui-version.json +6 -0
- solace_agent_mesh/common/a2a/events.py +2 -1
- solace_agent_mesh/common/a2a/protocol.py +5 -0
- solace_agent_mesh/common/a2a/types.py +2 -1
- solace_agent_mesh/common/a2a_spec/schemas/artifact_creation_progress.json +23 -6
- solace_agent_mesh/common/a2a_spec/schemas/feedback_event.json +51 -0
- solace_agent_mesh/common/agent_registry.py +38 -11
- solace_agent_mesh/common/data_parts.py +144 -4
- solace_agent_mesh/common/error_handlers.py +83 -0
- solace_agent_mesh/common/exceptions.py +24 -0
- solace_agent_mesh/common/oauth/__init__.py +17 -0
- solace_agent_mesh/common/oauth/oauth_client.py +408 -0
- solace_agent_mesh/common/oauth/utils.py +50 -0
- solace_agent_mesh/common/rag_dto.py +156 -0
- solace_agent_mesh/common/sac/sam_component_base.py +97 -19
- solace_agent_mesh/common/sam_events/event_service.py +2 -2
- solace_agent_mesh/common/services/employee_service.py +1 -1
- solace_agent_mesh/common/utils/embeds/constants.py +1 -0
- solace_agent_mesh/common/utils/embeds/converter.py +1 -8
- solace_agent_mesh/common/utils/embeds/modifiers.py +4 -28
- solace_agent_mesh/common/utils/embeds/resolver.py +152 -31
- solace_agent_mesh/common/utils/embeds/types.py +9 -0
- solace_agent_mesh/common/utils/log_formatters.py +20 -0
- solace_agent_mesh/common/utils/mime_helpers.py +12 -5
- solace_agent_mesh/common/utils/pydantic_utils.py +90 -3
- solace_agent_mesh/common/utils/rbac_utils.py +69 -0
- solace_agent_mesh/common/utils/templates/__init__.py +8 -0
- solace_agent_mesh/common/utils/templates/liquid_renderer.py +210 -0
- solace_agent_mesh/common/utils/templates/template_resolver.py +161 -0
- solace_agent_mesh/config_portal/backend/common.py +12 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-CljP4_mv.js +103 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/{components-Rk0n-9cK.js → components-CaC6hG8d.js} +22 -22
- solace_agent_mesh/config_portal/frontend/static/client/assets/{entry.client-mvZjNKiz.js → entry.client-H_TM0YBt.js} +3 -3
- solace_agent_mesh/config_portal/frontend/static/client/assets/{index-DzNKzXrc.js → index-CnFykb2v.js} +16 -16
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-f8439d40.js +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-BIMqslJB.css +1 -0
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-mJmTIdIk.js +10 -0
- solace_agent_mesh/config_portal/frontend/static/client/index.html +3 -3
- solace_agent_mesh/core_a2a/service.py +3 -2
- solace_agent_mesh/gateway/adapter/__init__.py +1 -0
- solace_agent_mesh/gateway/adapter/base.py +170 -0
- solace_agent_mesh/gateway/adapter/types.py +230 -0
- solace_agent_mesh/gateway/base/app.py +39 -2
- solace_agent_mesh/gateway/base/auth_interface.py +103 -0
- solace_agent_mesh/gateway/base/component.py +1027 -151
- solace_agent_mesh/gateway/generic/__init__.py +1 -0
- solace_agent_mesh/gateway/generic/app.py +50 -0
- solace_agent_mesh/gateway/generic/component.py +894 -0
- solace_agent_mesh/gateway/http_sse/alembic/env.py +0 -7
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_project_users_table.py +72 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251023_add_soft_delete_and_search.py +109 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_default_agent_to_projects.py +26 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251024_add_projects_table.py +135 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251108_create_prompt_tables_with_sharing.py +154 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251115_add_parent_task_id.py +32 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251126_add_background_task_fields.py +47 -0
- solace_agent_mesh/gateway/http_sse/alembic/versions/20251202_add_versioned_fields_to_prompts.py +52 -0
- solace_agent_mesh/gateway/http_sse/alembic.ini +0 -36
- solace_agent_mesh/gateway/http_sse/app.py +40 -11
- solace_agent_mesh/gateway/http_sse/component.py +285 -160
- solace_agent_mesh/gateway/http_sse/dependencies.py +149 -114
- solace_agent_mesh/gateway/http_sse/main.py +68 -450
- solace_agent_mesh/gateway/http_sse/repository/__init__.py +19 -1
- solace_agent_mesh/gateway/http_sse/repository/chat_task_repository.py +2 -2
- solace_agent_mesh/gateway/http_sse/repository/entities/project.py +81 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/project_user.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/entities/session.py +26 -3
- solace_agent_mesh/gateway/http_sse/repository/entities/task.py +7 -0
- solace_agent_mesh/gateway/http_sse/repository/feedback_repository.py +47 -0
- solace_agent_mesh/gateway/http_sse/repository/interfaces.py +114 -6
- solace_agent_mesh/gateway/http_sse/repository/models/__init__.py +13 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_model.py +51 -0
- solace_agent_mesh/gateway/http_sse/repository/models/project_user_model.py +75 -0
- solace_agent_mesh/gateway/http_sse/repository/models/prompt_model.py +159 -0
- solace_agent_mesh/gateway/http_sse/repository/models/session_model.py +8 -2
- solace_agent_mesh/gateway/http_sse/repository/models/task_model.py +8 -1
- solace_agent_mesh/gateway/http_sse/repository/project_repository.py +172 -0
- solace_agent_mesh/gateway/http_sse/repository/project_user_repository.py +186 -0
- solace_agent_mesh/gateway/http_sse/repository/session_repository.py +177 -11
- solace_agent_mesh/gateway/http_sse/repository/task_repository.py +86 -2
- solace_agent_mesh/gateway/http_sse/routers/agent_cards.py +38 -7
- solace_agent_mesh/gateway/http_sse/routers/artifacts.py +256 -58
- solace_agent_mesh/gateway/http_sse/routers/auth.py +168 -134
- solace_agent_mesh/gateway/http_sse/routers/config.py +302 -8
- solace_agent_mesh/gateway/http_sse/routers/dto/project_dto.py +69 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/prompt_dto.py +255 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/project_requests.py +48 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/session_requests.py +14 -1
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/base_responses.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/project_responses.py +31 -0
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/session_responses.py +5 -2
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/version_responses.py +31 -0
- solace_agent_mesh/gateway/http_sse/routers/feedback.py +133 -2
- solace_agent_mesh/gateway/http_sse/routers/people.py +2 -2
- solace_agent_mesh/gateway/http_sse/routers/projects.py +768 -0
- solace_agent_mesh/gateway/http_sse/routers/prompts.py +1416 -0
- solace_agent_mesh/gateway/http_sse/routers/sessions.py +167 -7
- solace_agent_mesh/gateway/http_sse/routers/speech.py +355 -0
- solace_agent_mesh/gateway/http_sse/routers/sse.py +131 -8
- solace_agent_mesh/gateway/http_sse/routers/tasks.py +670 -18
- solace_agent_mesh/gateway/http_sse/routers/users.py +1 -1
- solace_agent_mesh/gateway/http_sse/routers/version.py +343 -0
- solace_agent_mesh/gateway/http_sse/routers/visualization.py +92 -9
- solace_agent_mesh/gateway/http_sse/services/audio_service.py +1227 -0
- solace_agent_mesh/gateway/http_sse/services/background_task_monitor.py +186 -0
- solace_agent_mesh/gateway/http_sse/services/data_retention_service.py +1 -1
- solace_agent_mesh/gateway/http_sse/services/feedback_service.py +1 -1
- solace_agent_mesh/gateway/http_sse/services/project_service.py +930 -0
- solace_agent_mesh/gateway/http_sse/services/prompt_builder_assistant.py +303 -0
- solace_agent_mesh/gateway/http_sse/services/session_service.py +361 -12
- solace_agent_mesh/gateway/http_sse/services/task_logger_service.py +354 -4
- solace_agent_mesh/gateway/http_sse/session_manager.py +15 -15
- solace_agent_mesh/gateway/http_sse/sse_manager.py +286 -166
- solace_agent_mesh/gateway/http_sse/utils/artifact_copy_utils.py +370 -0
- solace_agent_mesh/gateway/http_sse/utils/stim_utils.py +41 -1
- solace_agent_mesh/services/__init__.py +0 -0
- solace_agent_mesh/services/platform/__init__.py +29 -0
- solace_agent_mesh/services/platform/alembic/env.py +85 -0
- solace_agent_mesh/services/platform/alembic/script.py.mako +28 -0
- solace_agent_mesh/services/platform/alembic.ini +109 -0
- solace_agent_mesh/services/platform/api/__init__.py +3 -0
- solace_agent_mesh/services/platform/api/dependencies.py +154 -0
- solace_agent_mesh/services/platform/api/main.py +314 -0
- solace_agent_mesh/services/platform/api/middleware.py +51 -0
- solace_agent_mesh/services/platform/api/routers/__init__.py +33 -0
- solace_agent_mesh/services/platform/api/routers/health_router.py +31 -0
- solace_agent_mesh/services/platform/app.py +215 -0
- solace_agent_mesh/services/platform/component.py +777 -0
- solace_agent_mesh/shared/__init__.py +14 -0
- solace_agent_mesh/shared/api/__init__.py +42 -0
- solace_agent_mesh/shared/auth/__init__.py +26 -0
- solace_agent_mesh/shared/auth/dependencies.py +204 -0
- solace_agent_mesh/shared/auth/middleware.py +347 -0
- solace_agent_mesh/shared/database/__init__.py +20 -0
- solace_agent_mesh/{gateway/http_sse/shared → shared/database}/base_repository.py +1 -1
- solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_exceptions.py +1 -1
- solace_agent_mesh/{gateway/http_sse/shared → shared/database}/database_helpers.py +1 -1
- solace_agent_mesh/shared/exceptions/__init__.py +36 -0
- solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exception_handlers.py +19 -5
- solace_agent_mesh/shared/utils/__init__.py +21 -0
- solace_agent_mesh/templates/logging_config_template.yaml +48 -0
- solace_agent_mesh/templates/main_orchestrator.yaml +12 -1
- solace_agent_mesh/templates/platform.yaml +49 -0
- solace_agent_mesh/templates/plugin_readme_template.md +3 -25
- solace_agent_mesh/templates/plugin_tool_config_template.yaml +109 -0
- solace_agent_mesh/templates/proxy_template.yaml +62 -0
- solace_agent_mesh/templates/webui.yaml +148 -6
- solace_agent_mesh/tools/web_search/__init__.py +18 -0
- solace_agent_mesh/tools/web_search/base.py +84 -0
- solace_agent_mesh/tools/web_search/google_search.py +247 -0
- solace_agent_mesh/tools/web_search/models.py +99 -0
- {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/METADATA +31 -12
- solace_agent_mesh-1.13.2.dist-info/RECORD +591 -0
- {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/WHEEL +1 -1
- solace_agent_mesh/agent/adk/adk_llm.txt +0 -232
- solace_agent_mesh/agent/adk/adk_llm_detail.txt +0 -566
- solace_agent_mesh/agent/adk/artifacts/artifacts_llm.txt +0 -171
- solace_agent_mesh/agent/adk/models/models_llm.txt +0 -142
- solace_agent_mesh/agent/agent_llm.txt +0 -378
- solace_agent_mesh/agent/agent_llm_detail.txt +0 -1702
- solace_agent_mesh/agent/protocol/protocol_llm.txt +0 -81
- solace_agent_mesh/agent/protocol/protocol_llm_detail.txt +0 -92
- solace_agent_mesh/agent/sac/sac_llm.txt +0 -189
- solace_agent_mesh/agent/sac/sac_llm_detail.txt +0 -200
- solace_agent_mesh/agent/testing/testing_llm.txt +0 -57
- solace_agent_mesh/agent/testing/testing_llm_detail.txt +0 -68
- solace_agent_mesh/agent/tools/tools_llm.txt +0 -263
- solace_agent_mesh/agent/tools/tools_llm_detail.txt +0 -274
- solace_agent_mesh/agent/utils/utils_llm.txt +0 -138
- solace_agent_mesh/agent/utils/utils_llm_detail.txt +0 -149
- solace_agent_mesh/assets/docs/assets/js/15ba94aa.932dd2db.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/17896441.a5e82f9b.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/240a0364.7eac6021.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/2e32b5e0.33f5d75b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3a6c6137.f5940cfa.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ac1795d.76654dd9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/3ff0015d.2be20244.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/547e15cc.2cbb060a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/55b7b518.f2b1d1ba.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/5c2bd65f.eda4bcb2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/631738c7.a8b1ef8b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6a520c9d.ba015d81.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6ad8f0bd.f4b15f3b.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/6d84eae0.4a5fbf39.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/71da7b71.38583438.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/8024126c.56e59919.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/81a99df0.07034dd9.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/82fbfb93.139a1a1f.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/924ffdeb.8095e148.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/94e8668d.b5ddb7a1.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/9bb13469.dd1c9b54.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/a94703ab.0438dbc2.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ab9708a8.3e6dd091.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/c93cbaa0.eaff365e.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/da0b5bad.d08a9466.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/dd817ffc.0aa9630a.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/dd81e2b8.d590bc9e.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/de915948.27d6b065.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e3d9abda.6b9493d0.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e6f9706b.e74a984d.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/e92d0134.cf6d6522.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/f284c35a.42f59cdd.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/ff4d71f2.15b02f97.js +0 -1
- solace_agent_mesh/assets/docs/assets/js/main.b12eac43.js +0 -2
- solace_agent_mesh/assets/docs/assets/js/runtime~main.e268214e.js +0 -1
- solace_agent_mesh/assets/docs/lunr-index-1761248203150.json +0 -1
- solace_agent_mesh/assets/docs/search-doc-1761248203150.json +0 -1
- solace_agent_mesh/cli/commands/add_cmd/add_cmd_llm.txt +0 -250
- solace_agent_mesh/cli/commands/init_cmd/init_cmd_llm.txt +0 -365
- solace_agent_mesh/cli/commands/plugin_cmd/plugin_cmd_llm.txt +0 -305
- solace_agent_mesh/client/webui/frontend/static/assets/client-CaY59VuC.js +0 -25
- solace_agent_mesh/client/webui/frontend/static/assets/main-B32noGmR.js +0 -342
- solace_agent_mesh/client/webui/frontend/static/assets/main-DHJKSW1S.css +0 -1
- solace_agent_mesh/client/webui/frontend/static/assets/vendor-BEmvJSYz.js +0 -405
- solace_agent_mesh/common/a2a/a2a_llm.txt +0 -182
- solace_agent_mesh/common/a2a/a2a_llm_detail.txt +0 -193
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm.txt +0 -407
- solace_agent_mesh/common/a2a_spec/a2a_spec_llm_detail.txt +0 -736
- solace_agent_mesh/common/a2a_spec/schemas/schemas_llm.txt +0 -313
- solace_agent_mesh/common/common_llm.txt +0 -251
- solace_agent_mesh/common/common_llm_detail.txt +0 -2562
- solace_agent_mesh/common/middleware/middleware_llm.txt +0 -174
- solace_agent_mesh/common/middleware/middleware_llm_detail.txt +0 -185
- solace_agent_mesh/common/sac/sac_llm.txt +0 -71
- solace_agent_mesh/common/sac/sac_llm_detail.txt +0 -82
- solace_agent_mesh/common/sam_events/sam_events_llm.txt +0 -104
- solace_agent_mesh/common/sam_events/sam_events_llm_detail.txt +0 -115
- solace_agent_mesh/common/services/providers/providers_llm.txt +0 -80
- solace_agent_mesh/common/services/services_llm.txt +0 -363
- solace_agent_mesh/common/services/services_llm_detail.txt +0 -459
- solace_agent_mesh/common/utils/embeds/embeds_llm.txt +0 -220
- solace_agent_mesh/common/utils/utils_llm.txt +0 -336
- solace_agent_mesh/common/utils/utils_llm_detail.txt +0 -572
- solace_agent_mesh/config_portal/frontend/static/client/assets/_index-ByU1X1HD.js +0 -98
- solace_agent_mesh/config_portal/frontend/static/client/assets/manifest-61038fc6.js +0 -1
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-BWvk5-gF.js +0 -10
- solace_agent_mesh/config_portal/frontend/static/client/assets/root-DxRwaWiE.css +0 -1
- solace_agent_mesh/core_a2a/core_a2a_llm.txt +0 -90
- solace_agent_mesh/core_a2a/core_a2a_llm_detail.txt +0 -101
- solace_agent_mesh/gateway/base/base_llm.txt +0 -224
- solace_agent_mesh/gateway/base/base_llm_detail.txt +0 -235
- solace_agent_mesh/gateway/gateway_llm.txt +0 -373
- solace_agent_mesh/gateway/gateway_llm_detail.txt +0 -3885
- solace_agent_mesh/gateway/http_sse/alembic/alembic_llm.txt +0 -295
- solace_agent_mesh/gateway/http_sse/alembic/versions/versions_llm.txt +0 -155
- solace_agent_mesh/gateway/http_sse/components/components_llm.txt +0 -105
- solace_agent_mesh/gateway/http_sse/http_sse_llm.txt +0 -299
- solace_agent_mesh/gateway/http_sse/http_sse_llm_detail.txt +0 -3278
- solace_agent_mesh/gateway/http_sse/repository/entities/entities_llm.txt +0 -263
- solace_agent_mesh/gateway/http_sse/repository/models/models_llm.txt +0 -266
- solace_agent_mesh/gateway/http_sse/repository/repository_llm.txt +0 -340
- solace_agent_mesh/gateway/http_sse/routers/dto/dto_llm.txt +0 -346
- solace_agent_mesh/gateway/http_sse/routers/dto/requests/requests_llm.txt +0 -83
- solace_agent_mesh/gateway/http_sse/routers/dto/responses/responses_llm.txt +0 -107
- solace_agent_mesh/gateway/http_sse/routers/routers_llm.txt +0 -314
- solace_agent_mesh/gateway/http_sse/services/services_llm.txt +0 -297
- solace_agent_mesh/gateway/http_sse/shared/__init__.py +0 -146
- solace_agent_mesh/gateway/http_sse/shared/shared_llm.txt +0 -285
- solace_agent_mesh/gateway/http_sse/utils/utils_llm.txt +0 -47
- solace_agent_mesh/llm.txt +0 -228
- solace_agent_mesh/llm_detail.txt +0 -2835
- solace_agent_mesh/solace_agent_mesh_llm.txt +0 -362
- solace_agent_mesh/solace_agent_mesh_llm_detail.txt +0 -8599
- solace_agent_mesh/templates/logging_config_template.ini +0 -45
- solace_agent_mesh/templates/templates_llm.txt +0 -147
- solace_agent_mesh-1.6.1.dist-info/RECORD +0 -525
- /solace_agent_mesh/assets/docs/assets/js/{main.b12eac43.js.LICENSE.txt → main.d634009f.js.LICENSE.txt} +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/auth_utils.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/pagination.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/api}/response_utils.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/error_dto.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/exceptions}/exceptions.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/enums.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/timestamp_utils.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/types.py +0 -0
- /solace_agent_mesh/{gateway/http_sse/shared → shared/utils}/utils.py +0 -0
- {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/entry_points.txt +0 -0
- {solace_agent_mesh-1.6.1.dist-info → solace_agent_mesh-1.13.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,8 +4,9 @@ Base Component class for Gateway implementations in the Solace AI Connector.
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import asyncio
|
|
7
|
-
import queue
|
|
8
7
|
import base64
|
|
8
|
+
import queue
|
|
9
|
+
import re
|
|
9
10
|
import uuid
|
|
10
11
|
from datetime import datetime, timezone
|
|
11
12
|
from typing import Any, Dict, Optional, List, Tuple, Union
|
|
@@ -21,7 +22,9 @@ from ...common.services.identity_service import (
|
|
|
21
22
|
create_identity_service,
|
|
22
23
|
)
|
|
23
24
|
from .task_context import TaskContextManager
|
|
25
|
+
from .auth_interface import AuthHandler
|
|
24
26
|
from ...common.a2a.types import ContentPart
|
|
27
|
+
from ...common.utils.rbac_utils import validate_agent_access
|
|
25
28
|
from a2a.types import (
|
|
26
29
|
Message as A2AMessage,
|
|
27
30
|
AgentCard,
|
|
@@ -31,20 +34,25 @@ from a2a.types import (
|
|
|
31
34
|
TaskArtifactUpdateEvent,
|
|
32
35
|
JSONRPCError,
|
|
33
36
|
TextPart,
|
|
37
|
+
DataPart,
|
|
34
38
|
FilePart,
|
|
35
39
|
FileWithBytes,
|
|
36
40
|
Artifact as A2AArtifact,
|
|
37
41
|
)
|
|
38
42
|
from ...common import a2a
|
|
39
|
-
from ...common.utils import is_text_based_mime_type
|
|
40
43
|
from ...common.utils.embeds import (
|
|
41
44
|
resolve_embeds_in_string,
|
|
42
|
-
resolve_embeds_recursively_in_string,
|
|
43
45
|
evaluate_embed,
|
|
44
46
|
LATE_EMBED_TYPES,
|
|
45
47
|
EARLY_EMBED_TYPES,
|
|
46
|
-
|
|
48
|
+
resolve_embeds_recursively_in_string,
|
|
47
49
|
)
|
|
50
|
+
from ...common.utils.embeds.types import ResolutionMode
|
|
51
|
+
from ...agent.utils.artifact_helpers import (
|
|
52
|
+
load_artifact_content_or_metadata,
|
|
53
|
+
format_artifact_uri,
|
|
54
|
+
)
|
|
55
|
+
from ...common.utils.mime_helpers import is_text_based_mime_type
|
|
48
56
|
from solace_ai_connector.common.message import (
|
|
49
57
|
Message as SolaceMessage,
|
|
50
58
|
)
|
|
@@ -97,9 +105,32 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
97
105
|
|
|
98
106
|
return super().get_config(key, default)
|
|
99
107
|
|
|
100
|
-
def __init__(
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
resolve_artifact_uris_in_gateway: bool = True,
|
|
111
|
+
supports_inline_artifact_resolution: bool = False,
|
|
112
|
+
filter_tool_data_parts: bool = True,
|
|
113
|
+
**kwargs: Any
|
|
114
|
+
):
|
|
115
|
+
"""
|
|
116
|
+
Initialize the BaseGatewayComponent.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
resolve_artifact_uris_in_gateway: If True, resolves artifact URIs before sending to external.
|
|
120
|
+
supports_inline_artifact_resolution: If True, SIGNAL_ARTIFACT_RETURN embeds are converted
|
|
121
|
+
to FileParts during embed resolution. If False (default), signals are passed through
|
|
122
|
+
for the gateway to handle manually. Use False for legacy gateways (e.g., Slack),
|
|
123
|
+
True for modern gateways that support inline artifact rendering (e.g., HTTP SSE).
|
|
124
|
+
filter_tool_data_parts: If True (default), filters out tool-related DataParts (tool_call,
|
|
125
|
+
tool_result, etc.) from final Task messages before sending to gateway. Use True for
|
|
126
|
+
gateways that don't want to display internal tool execution details (e.g., Slack),
|
|
127
|
+
False for gateways that display all parts (e.g., HTTP SSE Web UI).
|
|
128
|
+
**kwargs: Additional arguments passed to parent class.
|
|
129
|
+
"""
|
|
101
130
|
super().__init__(info, **kwargs)
|
|
102
131
|
self.resolve_artifact_uris_in_gateway = resolve_artifact_uris_in_gateway
|
|
132
|
+
self.supports_inline_artifact_resolution = supports_inline_artifact_resolution
|
|
133
|
+
self.filter_tool_data_parts = filter_tool_data_parts
|
|
103
134
|
log.info("%s Initializing Base Gateway Component...", self.log_identifier)
|
|
104
135
|
|
|
105
136
|
try:
|
|
@@ -139,7 +170,9 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
139
170
|
|
|
140
171
|
self.agent_registry: AgentRegistry = AgentRegistry()
|
|
141
172
|
self.core_a2a_service: CoreA2AService = CoreA2AService(
|
|
142
|
-
agent_registry=self.agent_registry,
|
|
173
|
+
agent_registry=self.agent_registry,
|
|
174
|
+
namespace=self.namespace,
|
|
175
|
+
component_id="WebUI"
|
|
143
176
|
)
|
|
144
177
|
self.shared_artifact_service: Optional[BaseArtifactService] = (
|
|
145
178
|
initialize_artifact_service(self)
|
|
@@ -159,10 +192,65 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
159
192
|
self.log_identifier,
|
|
160
193
|
)
|
|
161
194
|
|
|
195
|
+
# Authentication handler (optional, enterprise feature)
|
|
196
|
+
self.auth_handler: Optional[AuthHandler] = None
|
|
197
|
+
|
|
198
|
+
# Setup authentication if enabled (subclasses override _setup_auth)
|
|
199
|
+
self._setup_auth()
|
|
200
|
+
|
|
162
201
|
log.info(
|
|
163
|
-
"%s Base Gateway Component
|
|
202
|
+
"%s Initialized Base Gateway Component.", self.log_identifier
|
|
164
203
|
)
|
|
165
204
|
|
|
205
|
+
def _setup_auth(self) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Setup authentication handler if enabled.
|
|
208
|
+
|
|
209
|
+
This method is called during initialization and can be overridden
|
|
210
|
+
by subclasses to customize auth setup. The default implementation
|
|
211
|
+
does nothing - subclasses should override to enable auth.
|
|
212
|
+
|
|
213
|
+
Example override in subclass:
|
|
214
|
+
def _setup_auth(self):
|
|
215
|
+
if self.get_config('enable_auth', False):
|
|
216
|
+
from enterprise.auth import SAMOAuth2Handler
|
|
217
|
+
self.auth_handler = SAMOAuth2Handler(self.config)
|
|
218
|
+
"""
|
|
219
|
+
# Base implementation: no auth
|
|
220
|
+
# Subclasses (like GenericGateway) override to enable auth
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
async def _inject_auth_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
|
|
224
|
+
"""
|
|
225
|
+
Inject authentication headers if authenticated.
|
|
226
|
+
|
|
227
|
+
This helper method should be called before making outgoing HTTP requests
|
|
228
|
+
to add authentication headers (e.g., Bearer tokens) to the request.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
headers: Existing headers dictionary
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Headers dictionary with auth headers added (if authenticated)
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
headers = {"Content-Type": "application/json"}
|
|
238
|
+
headers = await self._inject_auth_headers(headers)
|
|
239
|
+
# headers now includes Authorization if authenticated
|
|
240
|
+
"""
|
|
241
|
+
if self.auth_handler:
|
|
242
|
+
try:
|
|
243
|
+
auth_headers = await self.auth_handler.get_auth_headers()
|
|
244
|
+
headers.update(auth_headers)
|
|
245
|
+
except Exception as e:
|
|
246
|
+
log.warning(
|
|
247
|
+
"%s Failed to get auth headers: %s",
|
|
248
|
+
self.log_identifier,
|
|
249
|
+
e
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return headers
|
|
253
|
+
|
|
166
254
|
async def authenticate_and_enrich_user(
|
|
167
255
|
self, external_event_data: Any
|
|
168
256
|
) -> Optional[Dict[str, Any]]:
|
|
@@ -208,6 +296,8 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
208
296
|
user_identity: Any,
|
|
209
297
|
is_streaming: bool = True,
|
|
210
298
|
api_version: str = "v2",
|
|
299
|
+
task_id_override: str | None = None,
|
|
300
|
+
metadata: dict[str, Any] | None = None,
|
|
211
301
|
) -> str:
|
|
212
302
|
log_id_prefix = f"{self.log_identifier}[SubmitA2ATask]"
|
|
213
303
|
log.info(
|
|
@@ -241,7 +331,7 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
241
331
|
user_config = await config_resolver.resolve_user_config(
|
|
242
332
|
user_identity, gateway_context, {}
|
|
243
333
|
)
|
|
244
|
-
log.
|
|
334
|
+
log.debug(
|
|
245
335
|
"%s Resolved user configuration for user_identity '%s': %s",
|
|
246
336
|
log_id_prefix,
|
|
247
337
|
user_identity.get("id"),
|
|
@@ -258,13 +348,26 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
258
348
|
|
|
259
349
|
user_config["user_profile"] = user_identity
|
|
260
350
|
|
|
351
|
+
# Validate user has permission to access this target agent
|
|
352
|
+
validate_agent_access(
|
|
353
|
+
user_config=user_config,
|
|
354
|
+
target_agent_name=target_agent_name,
|
|
355
|
+
validation_context={
|
|
356
|
+
"gateway_id": self.gateway_id,
|
|
357
|
+
"source": "gateway_request",
|
|
358
|
+
},
|
|
359
|
+
log_identifier=log_id_prefix,
|
|
360
|
+
)
|
|
361
|
+
|
|
261
362
|
external_request_context["user_identity"] = user_identity
|
|
262
363
|
external_request_context["a2a_user_config"] = user_config
|
|
263
364
|
external_request_context["api_version"] = api_version
|
|
365
|
+
external_request_context["is_streaming"] = is_streaming
|
|
264
366
|
log.debug(
|
|
265
|
-
"%s Stored user_identity, configuration, and
|
|
367
|
+
"%s Stored user_identity, configuration, api_version (%s), and is_streaming (%s) in external_request_context.",
|
|
266
368
|
log_id_prefix,
|
|
267
369
|
api_version,
|
|
370
|
+
is_streaming,
|
|
268
371
|
)
|
|
269
372
|
|
|
270
373
|
now = datetime.now(timezone.utc)
|
|
@@ -299,6 +402,15 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
299
402
|
"system_purpose": system_purpose,
|
|
300
403
|
"response_format": response_format,
|
|
301
404
|
}
|
|
405
|
+
|
|
406
|
+
# Add session behavior if provided by adapter
|
|
407
|
+
session_behavior = external_request_context.get("session_behavior")
|
|
408
|
+
if session_behavior:
|
|
409
|
+
a2a_metadata["sessionBehavior"] = session_behavior
|
|
410
|
+
log.debug(
|
|
411
|
+
"%s Setting sessionBehavior to: %s", log_id_prefix, session_behavior
|
|
412
|
+
)
|
|
413
|
+
|
|
302
414
|
invoked_artifacts = external_request_context.get("invoked_with_artifacts")
|
|
303
415
|
if invoked_artifacts:
|
|
304
416
|
a2a_metadata["invoked_with_artifacts"] = invoked_artifacts
|
|
@@ -307,9 +419,15 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
307
419
|
log_id_prefix,
|
|
308
420
|
len(invoked_artifacts),
|
|
309
421
|
)
|
|
422
|
+
|
|
423
|
+
if metadata:
|
|
424
|
+
a2a_metadata.update(metadata)
|
|
310
425
|
|
|
311
426
|
# This correlation ID is used by the gateway to track the task
|
|
312
|
-
|
|
427
|
+
if task_id_override:
|
|
428
|
+
task_id = task_id_override
|
|
429
|
+
else:
|
|
430
|
+
task_id = f"gdk-task-{uuid.uuid4().hex}"
|
|
313
431
|
|
|
314
432
|
prepared_a2a_parts = await self._prepare_parts_for_publishing(
|
|
315
433
|
parts=a2a_parts,
|
|
@@ -450,7 +568,7 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
450
568
|
async def _handle_resolved_signals(
|
|
451
569
|
self,
|
|
452
570
|
external_request_context: Dict,
|
|
453
|
-
signals: List[Tuple[
|
|
571
|
+
signals: List[Tuple[None, str, Any]],
|
|
454
572
|
original_rpc_id: Optional[str],
|
|
455
573
|
is_finalizing_context: bool = False,
|
|
456
574
|
):
|
|
@@ -458,7 +576,7 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
458
576
|
if not signals:
|
|
459
577
|
return
|
|
460
578
|
|
|
461
|
-
for
|
|
579
|
+
for signal_tuple in signals:
|
|
462
580
|
if (
|
|
463
581
|
isinstance(signal_tuple, tuple)
|
|
464
582
|
and len(signal_tuple) == 3
|
|
@@ -518,6 +636,375 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
518
636
|
log.exception(
|
|
519
637
|
"%s Error sending status signal: %s", log_id_prefix, e
|
|
520
638
|
)
|
|
639
|
+
elif signal_type == "SIGNAL_ARTIFACT_RETURN":
|
|
640
|
+
# Handle artifact return signal for legacy gateways
|
|
641
|
+
# During finalizing context (final Task), suppress this to avoid duplicates
|
|
642
|
+
# since the same signal might appear in both streaming and final responses
|
|
643
|
+
if is_finalizing_context:
|
|
644
|
+
log.debug(
|
|
645
|
+
"%s Suppressing SIGNAL_ARTIFACT_RETURN during finalizing context to avoid duplicate: %s",
|
|
646
|
+
log_id_prefix,
|
|
647
|
+
signal_data,
|
|
648
|
+
)
|
|
649
|
+
continue
|
|
650
|
+
|
|
651
|
+
log.info(
|
|
652
|
+
"%s Handling SIGNAL_ARTIFACT_RETURN for legacy gateway: %s",
|
|
653
|
+
log_id_prefix,
|
|
654
|
+
signal_data,
|
|
655
|
+
)
|
|
656
|
+
try:
|
|
657
|
+
filename = signal_data.get("filename")
|
|
658
|
+
version = signal_data.get("version")
|
|
659
|
+
|
|
660
|
+
if not filename:
|
|
661
|
+
log.error(
|
|
662
|
+
"%s SIGNAL_ARTIFACT_RETURN missing filename. Skipping.",
|
|
663
|
+
log_id_prefix,
|
|
664
|
+
)
|
|
665
|
+
continue
|
|
666
|
+
|
|
667
|
+
# Load artifact content (not just metadata) for legacy gateways
|
|
668
|
+
# Legacy gateways like Slack need the actual bytes to upload files
|
|
669
|
+
artifact_data = await load_artifact_content_or_metadata(
|
|
670
|
+
self.shared_artifact_service,
|
|
671
|
+
app_name=external_request_context.get(
|
|
672
|
+
"app_name_for_artifacts", self.gateway_id
|
|
673
|
+
),
|
|
674
|
+
user_id=external_request_context.get("user_id_for_artifacts"),
|
|
675
|
+
session_id=external_request_context.get("a2a_session_id"),
|
|
676
|
+
filename=filename,
|
|
677
|
+
version=version,
|
|
678
|
+
load_metadata_only=False, # Load full content for legacy gateways
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
if artifact_data.get("status") != "success":
|
|
682
|
+
log.error(
|
|
683
|
+
"%s Failed to load artifact content for %s v%s",
|
|
684
|
+
log_id_prefix,
|
|
685
|
+
filename,
|
|
686
|
+
version,
|
|
687
|
+
)
|
|
688
|
+
continue
|
|
689
|
+
|
|
690
|
+
# Get content and ensure it's bytes
|
|
691
|
+
content = artifact_data.get("content")
|
|
692
|
+
if not content:
|
|
693
|
+
log.error(
|
|
694
|
+
"%s No content found in artifact %s v%s",
|
|
695
|
+
log_id_prefix,
|
|
696
|
+
filename,
|
|
697
|
+
version,
|
|
698
|
+
)
|
|
699
|
+
continue
|
|
700
|
+
|
|
701
|
+
# Convert to bytes if it's a string (text-based artifacts)
|
|
702
|
+
if isinstance(content, str):
|
|
703
|
+
content_bytes = content.encode("utf-8")
|
|
704
|
+
elif isinstance(content, bytes):
|
|
705
|
+
content_bytes = content
|
|
706
|
+
else:
|
|
707
|
+
log.error(
|
|
708
|
+
"%s Artifact content is neither string nor bytes: %s",
|
|
709
|
+
log_id_prefix,
|
|
710
|
+
type(content),
|
|
711
|
+
)
|
|
712
|
+
continue
|
|
713
|
+
|
|
714
|
+
# Resolve any late embeds inside the artifact content before returning.
|
|
715
|
+
content_bytes = await self._resolve_embeds_in_artifact_content(
|
|
716
|
+
content_bytes=content_bytes,
|
|
717
|
+
mime_type=artifact_data.get("metadata", {}).get(
|
|
718
|
+
"mime_type"
|
|
719
|
+
),
|
|
720
|
+
filename=filename,
|
|
721
|
+
external_request_context=external_request_context,
|
|
722
|
+
log_id_prefix=log_id_prefix,
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
# Create FilePart with bytes for legacy gateway to upload
|
|
726
|
+
file_part = a2a.create_file_part_from_bytes(
|
|
727
|
+
content_bytes=content_bytes,
|
|
728
|
+
name=filename,
|
|
729
|
+
mime_type=artifact_data.get("metadata", {}).get(
|
|
730
|
+
"mime_type"
|
|
731
|
+
),
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
# Create artifact with the file part
|
|
735
|
+
# Import Part type for wrapping
|
|
736
|
+
from a2a.types import Artifact, Part
|
|
737
|
+
artifact = Artifact(
|
|
738
|
+
artifact_id=str(uuid.uuid4().hex),
|
|
739
|
+
parts=[Part(root=file_part)],
|
|
740
|
+
name=filename,
|
|
741
|
+
description=f"Artifact: {filename}",
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
# Send as TaskArtifactUpdateEvent
|
|
745
|
+
a2a_task_id_for_signal = external_request_context.get(
|
|
746
|
+
"a2a_task_id_for_event", original_rpc_id
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
if not a2a_task_id_for_signal:
|
|
750
|
+
log.error(
|
|
751
|
+
"%s Cannot determine A2A task ID for artifact signal. Skipping.",
|
|
752
|
+
log_id_prefix,
|
|
753
|
+
)
|
|
754
|
+
continue
|
|
755
|
+
|
|
756
|
+
artifact_event = a2a.create_artifact_update(
|
|
757
|
+
task_id=a2a_task_id_for_signal,
|
|
758
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
759
|
+
artifact=artifact,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
await self._send_update_to_external(
|
|
763
|
+
external_request_context=external_request_context,
|
|
764
|
+
event_data=artifact_event,
|
|
765
|
+
is_final_chunk_of_update=False,
|
|
766
|
+
)
|
|
767
|
+
log.info(
|
|
768
|
+
"%s Sent artifact signal as TaskArtifactUpdateEvent for %s",
|
|
769
|
+
log_id_prefix,
|
|
770
|
+
filename,
|
|
771
|
+
)
|
|
772
|
+
except Exception as e:
|
|
773
|
+
log.exception(
|
|
774
|
+
"%s Error sending artifact signal: %s", log_id_prefix, e
|
|
775
|
+
)
|
|
776
|
+
elif signal_type == "SIGNAL_ARTIFACT_CREATION_COMPLETE":
|
|
777
|
+
# Handle artifact creation completion for legacy gateways
|
|
778
|
+
# This is similar to SIGNAL_ARTIFACT_RETURN but for newly created artifacts
|
|
779
|
+
log.info(
|
|
780
|
+
"%s Handling SIGNAL_ARTIFACT_CREATION_COMPLETE for legacy gateway: %s",
|
|
781
|
+
log_id_prefix,
|
|
782
|
+
signal_data,
|
|
783
|
+
)
|
|
784
|
+
try:
|
|
785
|
+
filename = signal_data.get("filename")
|
|
786
|
+
version = signal_data.get("version")
|
|
787
|
+
|
|
788
|
+
if not filename:
|
|
789
|
+
log.error(
|
|
790
|
+
"%s SIGNAL_ARTIFACT_CREATION_COMPLETE missing filename. Skipping.",
|
|
791
|
+
log_id_prefix,
|
|
792
|
+
)
|
|
793
|
+
continue
|
|
794
|
+
|
|
795
|
+
# Load artifact content (not just metadata) for legacy gateways
|
|
796
|
+
# Legacy gateways like Slack need the actual bytes to upload files
|
|
797
|
+
artifact_data = await load_artifact_content_or_metadata(
|
|
798
|
+
self.shared_artifact_service,
|
|
799
|
+
app_name=external_request_context.get(
|
|
800
|
+
"app_name_for_artifacts", self.gateway_id
|
|
801
|
+
),
|
|
802
|
+
user_id=external_request_context.get("user_id_for_artifacts"),
|
|
803
|
+
session_id=external_request_context.get("a2a_session_id"),
|
|
804
|
+
filename=filename,
|
|
805
|
+
version=version,
|
|
806
|
+
load_metadata_only=False, # Load full content for legacy gateways
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
if artifact_data.get("status") != "success":
|
|
810
|
+
log.error(
|
|
811
|
+
"%s Failed to load artifact content for %s v%s",
|
|
812
|
+
log_id_prefix,
|
|
813
|
+
filename,
|
|
814
|
+
version,
|
|
815
|
+
)
|
|
816
|
+
continue
|
|
817
|
+
|
|
818
|
+
# Get content and ensure it's bytes
|
|
819
|
+
content = artifact_data.get("content")
|
|
820
|
+
if not content:
|
|
821
|
+
log.error(
|
|
822
|
+
"%s No content found in artifact %s v%s",
|
|
823
|
+
log_id_prefix,
|
|
824
|
+
filename,
|
|
825
|
+
version,
|
|
826
|
+
)
|
|
827
|
+
continue
|
|
828
|
+
|
|
829
|
+
# Convert to bytes if it's a string (text-based artifacts)
|
|
830
|
+
if isinstance(content, str):
|
|
831
|
+
content_bytes = content.encode("utf-8")
|
|
832
|
+
elif isinstance(content, bytes):
|
|
833
|
+
content_bytes = content
|
|
834
|
+
else:
|
|
835
|
+
log.error(
|
|
836
|
+
"%s Artifact content is neither string nor bytes: %s",
|
|
837
|
+
log_id_prefix,
|
|
838
|
+
type(content),
|
|
839
|
+
)
|
|
840
|
+
continue
|
|
841
|
+
|
|
842
|
+
# Create FilePart with bytes for legacy gateway to upload
|
|
843
|
+
file_part = a2a.create_file_part_from_bytes(
|
|
844
|
+
content_bytes=content_bytes,
|
|
845
|
+
name=filename,
|
|
846
|
+
mime_type=signal_data.get("mime_type") or artifact_data.get("metadata", {}).get("mime_type"),
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
# Create artifact with the file part
|
|
850
|
+
# Import Part type for wrapping
|
|
851
|
+
from a2a.types import Artifact, Part
|
|
852
|
+
artifact = Artifact(
|
|
853
|
+
artifact_id=str(uuid.uuid4().hex),
|
|
854
|
+
parts=[Part(root=file_part)],
|
|
855
|
+
name=filename,
|
|
856
|
+
description=f"Artifact: {filename}",
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
# Send as TaskArtifactUpdateEvent
|
|
860
|
+
a2a_task_id_for_signal = external_request_context.get(
|
|
861
|
+
"a2a_task_id_for_event", original_rpc_id
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
if not a2a_task_id_for_signal:
|
|
865
|
+
log.error(
|
|
866
|
+
"%s Cannot determine A2A task ID for artifact creation signal. Skipping.",
|
|
867
|
+
log_id_prefix,
|
|
868
|
+
)
|
|
869
|
+
continue
|
|
870
|
+
|
|
871
|
+
artifact_event = a2a.create_artifact_update(
|
|
872
|
+
task_id=a2a_task_id_for_signal,
|
|
873
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
874
|
+
artifact=artifact,
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
await self._send_update_to_external(
|
|
878
|
+
external_request_context=external_request_context,
|
|
879
|
+
event_data=artifact_event,
|
|
880
|
+
is_final_chunk_of_update=False,
|
|
881
|
+
)
|
|
882
|
+
log.info(
|
|
883
|
+
"%s Sent artifact creation completion as TaskArtifactUpdateEvent for %s",
|
|
884
|
+
log_id_prefix,
|
|
885
|
+
filename,
|
|
886
|
+
)
|
|
887
|
+
except Exception as e:
|
|
888
|
+
log.exception(
|
|
889
|
+
"%s Error sending artifact creation completion signal: %s", log_id_prefix, e
|
|
890
|
+
)
|
|
891
|
+
elif signal_type == "SIGNAL_DEEP_RESEARCH_REPORT":
|
|
892
|
+
# Handle deep research report signal for legacy gateways
|
|
893
|
+
# For legacy gateways, we send the report as a file attachment
|
|
894
|
+
if is_finalizing_context:
|
|
895
|
+
log.debug(
|
|
896
|
+
"%s Suppressing SIGNAL_DEEP_RESEARCH_REPORT during finalizing context to avoid duplicate: %s",
|
|
897
|
+
log_id_prefix,
|
|
898
|
+
signal_data,
|
|
899
|
+
)
|
|
900
|
+
continue
|
|
901
|
+
|
|
902
|
+
try:
|
|
903
|
+
filename = signal_data.get("filename")
|
|
904
|
+
version = signal_data.get("version")
|
|
905
|
+
|
|
906
|
+
if not filename:
|
|
907
|
+
log.error(
|
|
908
|
+
"%s SIGNAL_DEEP_RESEARCH_REPORT missing filename. Skipping.",
|
|
909
|
+
log_id_prefix,
|
|
910
|
+
)
|
|
911
|
+
continue
|
|
912
|
+
|
|
913
|
+
# Load artifact content for legacy gateways
|
|
914
|
+
artifact_data = await load_artifact_content_or_metadata(
|
|
915
|
+
self.shared_artifact_service,
|
|
916
|
+
app_name=external_request_context.get(
|
|
917
|
+
"app_name_for_artifacts", self.gateway_id
|
|
918
|
+
),
|
|
919
|
+
user_id=external_request_context.get("user_id_for_artifacts"),
|
|
920
|
+
session_id=external_request_context.get("a2a_session_id"),
|
|
921
|
+
filename=filename,
|
|
922
|
+
version=version,
|
|
923
|
+
load_metadata_only=False,
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
if artifact_data.get("status") != "success":
|
|
927
|
+
log.error(
|
|
928
|
+
"%s Failed to load deep research report content for %s v%s",
|
|
929
|
+
log_id_prefix,
|
|
930
|
+
filename,
|
|
931
|
+
version,
|
|
932
|
+
)
|
|
933
|
+
continue
|
|
934
|
+
|
|
935
|
+
content = artifact_data.get("content")
|
|
936
|
+
if not content:
|
|
937
|
+
log.error(
|
|
938
|
+
"%s No content found in deep research report %s v%s",
|
|
939
|
+
log_id_prefix,
|
|
940
|
+
filename,
|
|
941
|
+
version,
|
|
942
|
+
)
|
|
943
|
+
continue
|
|
944
|
+
|
|
945
|
+
# Convert to bytes if it's a string
|
|
946
|
+
if isinstance(content, str):
|
|
947
|
+
content_bytes = content.encode("utf-8")
|
|
948
|
+
elif isinstance(content, bytes):
|
|
949
|
+
content_bytes = content
|
|
950
|
+
else:
|
|
951
|
+
log.error(
|
|
952
|
+
"%s Deep research report content is neither string nor bytes: %s",
|
|
953
|
+
log_id_prefix,
|
|
954
|
+
type(content),
|
|
955
|
+
)
|
|
956
|
+
continue
|
|
957
|
+
|
|
958
|
+
# Create FilePart with bytes for legacy gateway to upload
|
|
959
|
+
file_part = a2a.create_file_part_from_bytes(
|
|
960
|
+
content_bytes=content_bytes,
|
|
961
|
+
name=filename,
|
|
962
|
+
mime_type=artifact_data.get("metadata", {}).get(
|
|
963
|
+
"mime_type", "text/markdown"
|
|
964
|
+
),
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
# Create artifact with the file part
|
|
968
|
+
from a2a.types import Artifact, Part
|
|
969
|
+
artifact = Artifact(
|
|
970
|
+
artifact_id=str(uuid.uuid4().hex),
|
|
971
|
+
parts=[Part(root=file_part)],
|
|
972
|
+
name=filename,
|
|
973
|
+
description=f"Deep Research Report: {filename}",
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
# Send as TaskArtifactUpdateEvent
|
|
977
|
+
a2a_task_id_for_signal = external_request_context.get(
|
|
978
|
+
"a2a_task_id_for_event", original_rpc_id
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
if not a2a_task_id_for_signal:
|
|
982
|
+
log.error(
|
|
983
|
+
"%s Cannot determine A2A task ID for deep research report signal. Skipping.",
|
|
984
|
+
log_id_prefix,
|
|
985
|
+
)
|
|
986
|
+
continue
|
|
987
|
+
|
|
988
|
+
artifact_event = a2a.create_artifact_update(
|
|
989
|
+
task_id=a2a_task_id_for_signal,
|
|
990
|
+
context_id=external_request_context.get("a2a_session_id"),
|
|
991
|
+
artifact=artifact,
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
await self._send_update_to_external(
|
|
995
|
+
external_request_context=external_request_context,
|
|
996
|
+
event_data=artifact_event,
|
|
997
|
+
is_final_chunk_of_update=False,
|
|
998
|
+
)
|
|
999
|
+
log.info(
|
|
1000
|
+
"%s Sent deep research report as TaskArtifactUpdateEvent for %s",
|
|
1001
|
+
log_id_prefix,
|
|
1002
|
+
filename,
|
|
1003
|
+
)
|
|
1004
|
+
except Exception as e:
|
|
1005
|
+
log.exception(
|
|
1006
|
+
"%s Error sending deep research report signal: %s", log_id_prefix, e
|
|
1007
|
+
)
|
|
521
1008
|
else:
|
|
522
1009
|
log.warning(
|
|
523
1010
|
"%s Received unhandled signal type during embed resolution: %s",
|
|
@@ -525,10 +1012,80 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
525
1012
|
signal_type,
|
|
526
1013
|
)
|
|
527
1014
|
|
|
528
|
-
async def
|
|
1015
|
+
async def _resolve_embeds_in_artifact_content(
|
|
1016
|
+
self,
|
|
1017
|
+
content_bytes: bytes,
|
|
1018
|
+
mime_type: Optional[str],
|
|
1019
|
+
filename: str,
|
|
1020
|
+
external_request_context: Dict[str, Any],
|
|
1021
|
+
log_id_prefix: str,
|
|
1022
|
+
) -> bytes:
|
|
1023
|
+
"""
|
|
1024
|
+
Checks if content is text-based and, if so, resolves late embeds within it.
|
|
1025
|
+
Returns the (potentially modified) content as bytes.
|
|
1026
|
+
"""
|
|
1027
|
+
if is_text_based_mime_type(mime_type):
|
|
1028
|
+
log.info(
|
|
1029
|
+
"%s Artifact '%s' is text-based (%s). Resolving late embeds.",
|
|
1030
|
+
log_id_prefix,
|
|
1031
|
+
filename,
|
|
1032
|
+
mime_type,
|
|
1033
|
+
)
|
|
1034
|
+
try:
|
|
1035
|
+
decoded_content = content_bytes.decode("utf-8")
|
|
1036
|
+
|
|
1037
|
+
# Construct context and config for the resolver
|
|
1038
|
+
embed_eval_context = {
|
|
1039
|
+
"artifact_service": self.shared_artifact_service,
|
|
1040
|
+
"session_context": {
|
|
1041
|
+
"app_name": external_request_context.get(
|
|
1042
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1043
|
+
),
|
|
1044
|
+
"user_id": external_request_context.get(
|
|
1045
|
+
"user_id_for_artifacts"
|
|
1046
|
+
),
|
|
1047
|
+
"session_id": external_request_context.get("a2a_session_id"),
|
|
1048
|
+
},
|
|
1049
|
+
}
|
|
1050
|
+
embed_eval_config = {
|
|
1051
|
+
"gateway_max_artifact_resolve_size_bytes": self.gateway_max_artifact_resolve_size_bytes,
|
|
1052
|
+
"gateway_recursive_embed_depth": self.gateway_recursive_embed_depth,
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
resolved_string = await resolve_embeds_recursively_in_string(
|
|
1056
|
+
text=decoded_content,
|
|
1057
|
+
context=embed_eval_context,
|
|
1058
|
+
resolver_func=evaluate_embed,
|
|
1059
|
+
types_to_resolve=LATE_EMBED_TYPES,
|
|
1060
|
+
resolution_mode=ResolutionMode.RECURSIVE_ARTIFACT_CONTENT,
|
|
1061
|
+
log_identifier=f"{log_id_prefix}[RecursiveResolve]",
|
|
1062
|
+
config=embed_eval_config,
|
|
1063
|
+
max_depth=self.gateway_recursive_embed_depth,
|
|
1064
|
+
)
|
|
1065
|
+
resolved_bytes = resolved_string.encode("utf-8")
|
|
1066
|
+
log.info(
|
|
1067
|
+
"%s Successfully resolved embeds in '%s'. New size: %d bytes.",
|
|
1068
|
+
log_id_prefix,
|
|
1069
|
+
filename,
|
|
1070
|
+
len(resolved_bytes),
|
|
1071
|
+
)
|
|
1072
|
+
return resolved_bytes
|
|
1073
|
+
except Exception as resolve_err:
|
|
1074
|
+
log.error(
|
|
1075
|
+
"%s Failed to resolve embeds within artifact '%s': %s. Returning raw content.",
|
|
1076
|
+
log_id_prefix,
|
|
1077
|
+
filename,
|
|
1078
|
+
resolve_err,
|
|
1079
|
+
)
|
|
1080
|
+
return content_bytes
|
|
1081
|
+
|
|
1082
|
+
async def _resolve_uri_in_file_part(
|
|
1083
|
+
self, file_part: FilePart, external_request_context: Dict[str, Any]
|
|
1084
|
+
):
|
|
529
1085
|
"""
|
|
530
1086
|
Checks if a FilePart has a resolvable URI and, if so,
|
|
531
1087
|
resolves it and mutates the part in-place by calling the common utility.
|
|
1088
|
+
After resolving the URI, it also resolves any late embeds within the content.
|
|
532
1089
|
"""
|
|
533
1090
|
await a2a.resolve_file_part_uri(
|
|
534
1091
|
part=file_part,
|
|
@@ -536,15 +1093,43 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
536
1093
|
log_identifier=self.log_identifier,
|
|
537
1094
|
)
|
|
538
1095
|
|
|
539
|
-
|
|
1096
|
+
# After resolving the URI to get the content, resolve any late embeds inside it.
|
|
1097
|
+
if file_part.file and isinstance(file_part.file, FileWithBytes):
|
|
1098
|
+
# The content is a base64 encoded string in the `bytes` attribute.
|
|
1099
|
+
# We need to decode it to raw bytes for processing.
|
|
1100
|
+
try:
|
|
1101
|
+
content_bytes = base64.b64decode(file_part.file.bytes)
|
|
1102
|
+
except Exception as e:
|
|
1103
|
+
log.error(
|
|
1104
|
+
"%s Failed to base64 decode file content for embed resolution: %s",
|
|
1105
|
+
f"{self.log_identifier}[UriResolve]",
|
|
1106
|
+
e,
|
|
1107
|
+
)
|
|
1108
|
+
return
|
|
1109
|
+
|
|
1110
|
+
resolved_bytes = await self._resolve_embeds_in_artifact_content(
|
|
1111
|
+
content_bytes=content_bytes,
|
|
1112
|
+
mime_type=file_part.file.mime_type,
|
|
1113
|
+
filename=file_part.file.name,
|
|
1114
|
+
external_request_context=external_request_context,
|
|
1115
|
+
log_id_prefix=f"{self.log_identifier}[UriResolve]",
|
|
1116
|
+
)
|
|
1117
|
+
# Re-encode the resolved content back to a base64 string for the FileWithBytes model.
|
|
1118
|
+
file_part.file.bytes = base64.b64encode(resolved_bytes).decode("utf-8")
|
|
1119
|
+
|
|
1120
|
+
async def _resolve_uris_in_parts_list(
|
|
1121
|
+
self, parts: List[ContentPart], external_request_context: Dict[str, Any]
|
|
1122
|
+
):
|
|
540
1123
|
"""Iterates over a list of part objects and resolves any FilePart URIs."""
|
|
541
1124
|
if not parts:
|
|
542
1125
|
return
|
|
543
1126
|
for part in parts:
|
|
544
1127
|
if isinstance(part, FilePart):
|
|
545
|
-
await self._resolve_uri_in_file_part(part)
|
|
1128
|
+
await self._resolve_uri_in_file_part(part, external_request_context)
|
|
546
1129
|
|
|
547
|
-
async def _resolve_uris_in_payload(
|
|
1130
|
+
async def _resolve_uris_in_payload(
|
|
1131
|
+
self, parsed_event: Any, external_request_context: Dict[str, Any]
|
|
1132
|
+
):
|
|
548
1133
|
"""
|
|
549
1134
|
Dispatcher that calls the appropriate targeted URI resolver based on the
|
|
550
1135
|
Pydantic model type of the event.
|
|
@@ -568,7 +1153,9 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
568
1153
|
parts_to_resolve.extend(a2a.get_parts_from_artifact(artifact))
|
|
569
1154
|
|
|
570
1155
|
if parts_to_resolve:
|
|
571
|
-
await self._resolve_uris_in_parts_list(
|
|
1156
|
+
await self._resolve_uris_in_parts_list(
|
|
1157
|
+
parts_to_resolve, external_request_context
|
|
1158
|
+
)
|
|
572
1159
|
else:
|
|
573
1160
|
log.debug(
|
|
574
1161
|
"%s Payload type '%s' did not yield any parts for URI resolution. Skipping.",
|
|
@@ -620,6 +1207,64 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
620
1207
|
processed_parts.append(part)
|
|
621
1208
|
return processed_parts
|
|
622
1209
|
|
|
1210
|
+
def _should_include_data_part_in_final_output(self, part: Any) -> bool:
|
|
1211
|
+
"""
|
|
1212
|
+
Determines if a DataPart should be included in the final output sent to the gateway.
|
|
1213
|
+
|
|
1214
|
+
This filters out internal/tool-related DataParts that shouldn't be shown to end users.
|
|
1215
|
+
Gateways can override this method for custom filtering logic.
|
|
1216
|
+
|
|
1217
|
+
Args:
|
|
1218
|
+
part: The part to check (expected to be a DataPart)
|
|
1219
|
+
|
|
1220
|
+
Returns:
|
|
1221
|
+
True if the part should be included, False if it should be filtered out
|
|
1222
|
+
"""
|
|
1223
|
+
from a2a.types import DataPart
|
|
1224
|
+
|
|
1225
|
+
if not isinstance(part, DataPart):
|
|
1226
|
+
return True
|
|
1227
|
+
|
|
1228
|
+
# Check if this is a tool result by looking at metadata
|
|
1229
|
+
# Tool results have metadata.tool_name set
|
|
1230
|
+
if part.metadata and part.metadata.get("tool_name"):
|
|
1231
|
+
# This is a tool result - filter it out
|
|
1232
|
+
return False
|
|
1233
|
+
|
|
1234
|
+
# Get the type of the data part
|
|
1235
|
+
data_type = part.data.get("type") if part.data else None
|
|
1236
|
+
|
|
1237
|
+
# Filter out tool-related data parts that are internal
|
|
1238
|
+
tool_related_types = {
|
|
1239
|
+
"tool_call",
|
|
1240
|
+
"tool_result",
|
|
1241
|
+
"tool_error",
|
|
1242
|
+
"tool_execution",
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
if data_type in tool_related_types:
|
|
1246
|
+
return False
|
|
1247
|
+
|
|
1248
|
+
# Handle artifact_creation_progress based on gateway capabilities
|
|
1249
|
+
if data_type == "artifact_creation_progress":
|
|
1250
|
+
# For modern gateways (HTTP SSE), keep these to display progress bubbles
|
|
1251
|
+
# For legacy gateways (Slack), filter them out as they'll be converted to FileParts
|
|
1252
|
+
if self.supports_inline_artifact_resolution:
|
|
1253
|
+
return True # Keep for HTTP SSE
|
|
1254
|
+
else:
|
|
1255
|
+
return False # Filter for Slack (will be converted to FileParts instead)
|
|
1256
|
+
|
|
1257
|
+
# Keep user-facing data parts like general progress updates
|
|
1258
|
+
user_facing_types = {
|
|
1259
|
+
"agent_progress_update",
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
if data_type in user_facing_types:
|
|
1263
|
+
return True
|
|
1264
|
+
|
|
1265
|
+
# Default: include unknown types (to avoid hiding potentially useful info)
|
|
1266
|
+
return True
|
|
1267
|
+
|
|
623
1268
|
async def _resolve_embeds_and_handle_signals(
|
|
624
1269
|
self,
|
|
625
1270
|
event_with_parts: Union[TaskStatusUpdateEvent, Task, TaskArtifactUpdateEvent],
|
|
@@ -628,17 +1273,11 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
628
1273
|
original_rpc_id: Optional[str],
|
|
629
1274
|
is_finalizing_context: bool = False,
|
|
630
1275
|
) -> bool:
|
|
631
|
-
"""
|
|
632
|
-
Resolves embeds and handles signals for an event containing parts.
|
|
633
|
-
Modifies event_with_parts in place if text content changes.
|
|
634
|
-
Manages stream buffer for TaskStatusUpdateEvent.
|
|
635
|
-
Returns True if the event content was modified or signals were handled, False otherwise.
|
|
636
|
-
"""
|
|
637
1276
|
if not self.enable_embed_resolution:
|
|
638
1277
|
return False
|
|
639
1278
|
|
|
640
1279
|
log_id_prefix = f"{self.log_identifier}[EmbedResolve:{a2a_task_id}]"
|
|
641
|
-
|
|
1280
|
+
content_modified = False
|
|
642
1281
|
|
|
643
1282
|
embed_eval_context = {
|
|
644
1283
|
"artifact_service": self.shared_artifact_service,
|
|
@@ -656,8 +1295,6 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
656
1295
|
}
|
|
657
1296
|
|
|
658
1297
|
parts_owner: Optional[Union[A2AMessage, A2AArtifact]] = None
|
|
659
|
-
is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
|
|
660
|
-
|
|
661
1298
|
if isinstance(event_with_parts, (TaskStatusUpdateEvent, Task)):
|
|
662
1299
|
if event_with_parts.status and event_with_parts.status.message:
|
|
663
1300
|
parts_owner = event_with_parts.status.message
|
|
@@ -665,146 +1302,359 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
665
1302
|
if event_with_parts.artifact:
|
|
666
1303
|
parts_owner = event_with_parts.artifact
|
|
667
1304
|
|
|
668
|
-
if parts_owner and parts_owner.parts:
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1305
|
+
if not (parts_owner and parts_owner.parts):
|
|
1306
|
+
return False
|
|
1307
|
+
|
|
1308
|
+
is_streaming_status_update = isinstance(event_with_parts, TaskStatusUpdateEvent)
|
|
1309
|
+
stream_buffer_key = f"{a2a_task_id}_stream_buffer"
|
|
1310
|
+
current_buffer = ""
|
|
1311
|
+
if is_streaming_status_update:
|
|
1312
|
+
current_buffer = (
|
|
1313
|
+
self.task_context_manager.get_context(stream_buffer_key) or ""
|
|
1314
|
+
)
|
|
1315
|
+
|
|
1316
|
+
original_parts: List[ContentPart] = (
|
|
1317
|
+
a2a.get_parts_from_message(parts_owner)
|
|
1318
|
+
if isinstance(parts_owner, A2AMessage)
|
|
1319
|
+
else a2a.get_parts_from_artifact(parts_owner)
|
|
1320
|
+
)
|
|
672
1321
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1322
|
+
new_parts: List[ContentPart] = []
|
|
1323
|
+
other_signals = []
|
|
1324
|
+
|
|
1325
|
+
for part in original_parts:
|
|
1326
|
+
if isinstance(part, TextPart) and part.text:
|
|
1327
|
+
text_to_resolve = current_buffer + part.text
|
|
1328
|
+
current_buffer = "" # Buffer is now being processed
|
|
1329
|
+
|
|
1330
|
+
(
|
|
1331
|
+
resolved_text,
|
|
1332
|
+
processed_idx,
|
|
1333
|
+
signals_with_placeholders,
|
|
1334
|
+
) = await resolve_embeds_in_string(
|
|
1335
|
+
text=text_to_resolve,
|
|
1336
|
+
context=embed_eval_context,
|
|
1337
|
+
resolver_func=evaluate_embed,
|
|
1338
|
+
types_to_resolve=LATE_EMBED_TYPES.union({"status_update"}),
|
|
1339
|
+
resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
|
|
1340
|
+
log_identifier=log_id_prefix,
|
|
1341
|
+
config=embed_eval_config,
|
|
676
1342
|
)
|
|
677
1343
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1344
|
+
if not signals_with_placeholders:
|
|
1345
|
+
new_parts.append(a2a.create_text_part(text=resolved_text))
|
|
1346
|
+
else:
|
|
1347
|
+
placeholder_map = {p: s for _, s, p in signals_with_placeholders}
|
|
1348
|
+
split_pattern = (
|
|
1349
|
+
f"({'|'.join(re.escape(p) for p in placeholder_map.keys())})"
|
|
1350
|
+
)
|
|
1351
|
+
text_fragments = re.split(split_pattern, resolved_text)
|
|
1352
|
+
|
|
1353
|
+
for i, fragment in enumerate(text_fragments):
|
|
1354
|
+
if not fragment:
|
|
1355
|
+
continue
|
|
1356
|
+
if fragment in placeholder_map:
|
|
1357
|
+
signal_tuple = placeholder_map[fragment]
|
|
1358
|
+
signal_type, signal_data = signal_tuple[1], signal_tuple[2]
|
|
1359
|
+
if signal_type == "SIGNAL_ARTIFACT_RETURN":
|
|
1360
|
+
# Only convert to FilePart if gateway supports inline artifact resolution
|
|
1361
|
+
if self.supports_inline_artifact_resolution:
|
|
1362
|
+
try:
|
|
1363
|
+
filename, version = (
|
|
1364
|
+
signal_data["filename"],
|
|
1365
|
+
signal_data["version"],
|
|
1366
|
+
)
|
|
1367
|
+
artifact_data = (
|
|
1368
|
+
await load_artifact_content_or_metadata(
|
|
1369
|
+
self.shared_artifact_service,
|
|
1370
|
+
**embed_eval_context["session_context"],
|
|
1371
|
+
filename=filename,
|
|
1372
|
+
version=version,
|
|
1373
|
+
load_metadata_only=True,
|
|
1374
|
+
)
|
|
1375
|
+
)
|
|
1376
|
+
if artifact_data.get("status") == "success":
|
|
1377
|
+
uri = format_artifact_uri(
|
|
1378
|
+
**embed_eval_context["session_context"],
|
|
1379
|
+
filename=filename,
|
|
1380
|
+
version=artifact_data.get("version"),
|
|
1381
|
+
)
|
|
1382
|
+
new_parts.append(
|
|
1383
|
+
a2a.create_file_part_from_uri(
|
|
1384
|
+
uri,
|
|
1385
|
+
filename,
|
|
1386
|
+
artifact_data.get("metadata", {}).get(
|
|
1387
|
+
"mime_type"
|
|
1388
|
+
),
|
|
1389
|
+
)
|
|
1390
|
+
)
|
|
1391
|
+
else:
|
|
1392
|
+
new_parts.append(
|
|
1393
|
+
a2a.create_text_part(
|
|
1394
|
+
f"[Error: Artifact '{filename}' v{version} not found.]"
|
|
1395
|
+
)
|
|
1396
|
+
)
|
|
1397
|
+
except Exception as e:
|
|
1398
|
+
log.exception(
|
|
1399
|
+
"%s Error handling SIGNAL_ARTIFACT_RETURN: %s",
|
|
1400
|
+
log_id_prefix,
|
|
1401
|
+
e,
|
|
1402
|
+
)
|
|
1403
|
+
new_parts.append(
|
|
1404
|
+
a2a.create_text_part(
|
|
1405
|
+
f"[Error: Could not retrieve artifact '{signal_data.get('filename')}'.]"
|
|
1406
|
+
)
|
|
1407
|
+
)
|
|
1408
|
+
else:
|
|
1409
|
+
# Legacy gateway mode: pass signal through for gateway to handle
|
|
1410
|
+
other_signals.append(signal_tuple)
|
|
1411
|
+
elif signal_type == "SIGNAL_DEEP_RESEARCH_REPORT":
|
|
1412
|
+
# Deep research reports should be rendered by the frontend component
|
|
1413
|
+
# For modern gateways (HTTP SSE), create a DataPart with artifact reference
|
|
1414
|
+
# For legacy gateways, pass through as signal
|
|
1415
|
+
if self.supports_inline_artifact_resolution:
|
|
1416
|
+
try:
|
|
1417
|
+
filename = signal_data["filename"]
|
|
1418
|
+
version = signal_data["version"]
|
|
1419
|
+
log.info(
|
|
1420
|
+
"%s Converting SIGNAL_DEEP_RESEARCH_REPORT to DataPart for frontend rendering: %s v%s",
|
|
1421
|
+
log_id_prefix,
|
|
1422
|
+
filename,
|
|
1423
|
+
version,
|
|
1424
|
+
)
|
|
1425
|
+
# Create a DataPart that the frontend can use to render DeepResearchReportBubble
|
|
1426
|
+
# The frontend will fetch the artifact content separately
|
|
1427
|
+
artifact_data = (
|
|
1428
|
+
await load_artifact_content_or_metadata(
|
|
1429
|
+
self.shared_artifact_service,
|
|
1430
|
+
**embed_eval_context["session_context"],
|
|
1431
|
+
filename=filename,
|
|
1432
|
+
version=version,
|
|
1433
|
+
load_metadata_only=True,
|
|
1434
|
+
)
|
|
1435
|
+
)
|
|
1436
|
+
if artifact_data.get("status") == "success":
|
|
1437
|
+
uri = format_artifact_uri(
|
|
1438
|
+
**embed_eval_context["session_context"],
|
|
1439
|
+
filename=filename,
|
|
1440
|
+
version=artifact_data.get("version"),
|
|
1441
|
+
)
|
|
1442
|
+
# Create a DataPart with deep_research_report type
|
|
1443
|
+
# This will be rendered by DeepResearchReportBubble in the frontend
|
|
1444
|
+
data_part = a2a.create_data_part(
|
|
1445
|
+
data={
|
|
1446
|
+
"type": "deep_research_report",
|
|
1447
|
+
"filename": filename,
|
|
1448
|
+
"version": artifact_data.get("version"),
|
|
1449
|
+
"uri": uri,
|
|
1450
|
+
},
|
|
1451
|
+
metadata={"source": "deep_research_tool"},
|
|
1452
|
+
)
|
|
1453
|
+
new_parts.append(data_part)
|
|
1454
|
+
else:
|
|
1455
|
+
new_parts.append(
|
|
1456
|
+
a2a.create_text_part(
|
|
1457
|
+
f"[Error: Deep research report '{filename}' v{version} not found.]"
|
|
1458
|
+
)
|
|
1459
|
+
)
|
|
1460
|
+
except Exception as e:
|
|
1461
|
+
log.exception(
|
|
1462
|
+
"%s Error handling SIGNAL_DEEP_RESEARCH_REPORT: %s",
|
|
1463
|
+
log_id_prefix,
|
|
1464
|
+
e,
|
|
1465
|
+
)
|
|
1466
|
+
new_parts.append(
|
|
1467
|
+
a2a.create_text_part(
|
|
1468
|
+
f"[Error: Could not retrieve deep research report '{signal_data.get('filename')}'.]"
|
|
1469
|
+
)
|
|
1470
|
+
)
|
|
1471
|
+
else:
|
|
1472
|
+
# Legacy gateway mode: pass signal through for gateway to handle
|
|
1473
|
+
other_signals.append(signal_tuple)
|
|
1474
|
+
elif signal_type == "SIGNAL_INLINE_BINARY_CONTENT":
|
|
1475
|
+
signal_data["content_bytes"] = signal_data.get("bytes")
|
|
1476
|
+
del signal_data["bytes"]
|
|
1477
|
+
new_parts.append(
|
|
1478
|
+
a2a.create_file_part_from_bytes(**signal_data)
|
|
1479
|
+
)
|
|
1480
|
+
else:
|
|
1481
|
+
other_signals.append(signal_tuple)
|
|
1482
|
+
else:
|
|
1483
|
+
# Check if the non-placeholder fragment is just whitespace
|
|
1484
|
+
# and is between two placeholders. If so, drop it.
|
|
1485
|
+
is_just_whitespace = not fragment.strip()
|
|
1486
|
+
prev_fragment_was_placeholder = (
|
|
1487
|
+
i > 0 and text_fragments[i - 1] in placeholder_map
|
|
1488
|
+
)
|
|
1489
|
+
next_fragment_is_placeholder = (
|
|
1490
|
+
i < len(text_fragments) - 1
|
|
1491
|
+
and text_fragments[i + 1] in placeholder_map
|
|
1492
|
+
)
|
|
683
1493
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1494
|
+
if (
|
|
1495
|
+
is_just_whitespace
|
|
1496
|
+
and prev_fragment_was_placeholder
|
|
1497
|
+
and next_fragment_is_placeholder
|
|
1498
|
+
):
|
|
1499
|
+
log.debug(
|
|
1500
|
+
"%s Dropping whitespace fragment between two file signals.",
|
|
1501
|
+
log_id_prefix,
|
|
1502
|
+
)
|
|
1503
|
+
continue
|
|
688
1504
|
|
|
689
|
-
|
|
690
|
-
current_buffer += part.text
|
|
691
|
-
text_to_resolve = current_buffer
|
|
1505
|
+
new_parts.append(a2a.create_text_part(text=fragment))
|
|
692
1506
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1507
|
+
if is_streaming_status_update:
|
|
1508
|
+
current_buffer = text_to_resolve[processed_idx:]
|
|
1509
|
+
|
|
1510
|
+
elif isinstance(part, FilePart) and part.file:
|
|
1511
|
+
# Handle recursive embeds in text-based FileParts
|
|
1512
|
+
new_parts.append(part) # Placeholder for now
|
|
1513
|
+
elif isinstance(part, DataPart):
|
|
1514
|
+
# Handle special DataPart types
|
|
1515
|
+
data_type = part.data.get("type") if part.data else None
|
|
1516
|
+
|
|
1517
|
+
if data_type == "template_block":
|
|
1518
|
+
# Resolve template block and replace with resolved text
|
|
1519
|
+
try:
|
|
1520
|
+
from ...common.utils.templates import resolve_template_blocks_in_string
|
|
1521
|
+
|
|
1522
|
+
# Reconstruct the template block syntax
|
|
1523
|
+
data_artifact = part.data.get("data_artifact", "")
|
|
1524
|
+
jsonpath = part.data.get("jsonpath")
|
|
1525
|
+
limit = part.data.get("limit")
|
|
1526
|
+
template_content = part.data.get("template_content", "")
|
|
1527
|
+
|
|
1528
|
+
# Build params string
|
|
1529
|
+
params_parts = [f'data="{data_artifact}"']
|
|
1530
|
+
if jsonpath:
|
|
1531
|
+
params_parts.append(f'jsonpath="{jsonpath}"')
|
|
1532
|
+
if limit is not None:
|
|
1533
|
+
params_parts.append(f'limit="{limit}"')
|
|
1534
|
+
params_str = " ".join(params_parts)
|
|
1535
|
+
|
|
1536
|
+
# Reconstruct full template block
|
|
1537
|
+
template_block = f"«««template: {params_str}\n{template_content}\n»»»"
|
|
1538
|
+
|
|
1539
|
+
log.debug(
|
|
1540
|
+
"%s Resolving template block inline: data=%s",
|
|
1541
|
+
log_id_prefix,
|
|
1542
|
+
data_artifact,
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
# Resolve the template
|
|
1546
|
+
resolved_text = await resolve_template_blocks_in_string(
|
|
1547
|
+
text=template_block,
|
|
1548
|
+
artifact_service=self.shared_artifact_service,
|
|
1549
|
+
session_context={
|
|
1550
|
+
"app_name": external_request_context.get(
|
|
1551
|
+
"app_name_for_artifacts", self.gateway_id
|
|
1552
|
+
),
|
|
1553
|
+
"user_id": external_request_context.get("user_id_for_artifacts"),
|
|
1554
|
+
"session_id": external_request_context.get("a2a_session_id"),
|
|
1555
|
+
},
|
|
1556
|
+
log_identifier=f"{log_id_prefix}[TemplateResolve]",
|
|
701
1557
|
)
|
|
702
|
-
)
|
|
703
1558
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
original_rpc_id,
|
|
709
|
-
is_finalizing_context,
|
|
1559
|
+
log.info(
|
|
1560
|
+
"%s Template resolved successfully. Output length: %d",
|
|
1561
|
+
log_id_prefix,
|
|
1562
|
+
len(resolved_text),
|
|
710
1563
|
)
|
|
711
|
-
content_modified_or_signal_handled = True
|
|
712
1564
|
|
|
713
|
-
|
|
1565
|
+
# Replace the DataPart with a TextPart containing the resolved content
|
|
714
1566
|
new_parts.append(a2a.create_text_part(text=resolved_text))
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
content_modified_or_signal_handled = True
|
|
720
|
-
|
|
721
|
-
if is_streaming_status_update:
|
|
722
|
-
current_buffer = text_to_resolve[processed_idx:]
|
|
723
|
-
elif (
|
|
724
|
-
processed_idx < len(text_to_resolve)
|
|
725
|
-
and not content_modified_or_signal_handled
|
|
726
|
-
):
|
|
727
|
-
log.warning(
|
|
728
|
-
"%s Unclosed embed in non-streaming TextPart. Remainder: '%s'",
|
|
1567
|
+
|
|
1568
|
+
except Exception as e:
|
|
1569
|
+
log.error(
|
|
1570
|
+
"%s Failed to resolve template block: %s",
|
|
729
1571
|
log_id_prefix,
|
|
730
|
-
|
|
1572
|
+
e,
|
|
1573
|
+
exc_info=True,
|
|
731
1574
|
)
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
resolver_func=evaluate_embed,
|
|
752
|
-
types_to_resolve=LATE_EMBED_TYPES,
|
|
753
|
-
log_identifier=log_id_prefix,
|
|
754
|
-
config=embed_eval_config,
|
|
755
|
-
max_depth=self.gateway_recursive_embed_depth,
|
|
756
|
-
)
|
|
757
|
-
)
|
|
758
|
-
if resolved_content != original_content:
|
|
759
|
-
new_file_content = part.file.model_copy()
|
|
760
|
-
new_file_content.bytes = base64.b64encode(
|
|
761
|
-
resolved_content.encode("utf-8")
|
|
762
|
-
).decode("utf-8")
|
|
763
|
-
new_parts.append(
|
|
764
|
-
FilePart(
|
|
765
|
-
file=new_file_content,
|
|
766
|
-
metadata=part.metadata,
|
|
767
|
-
)
|
|
768
|
-
)
|
|
769
|
-
content_modified_or_signal_handled = True
|
|
770
|
-
else:
|
|
771
|
-
new_parts.append(part)
|
|
772
|
-
else:
|
|
773
|
-
new_parts.append(part)
|
|
774
|
-
except Exception as e:
|
|
775
|
-
log.warning(
|
|
776
|
-
"%s Error during recursive FilePart resolution for %s: %s. Using original.",
|
|
1575
|
+
# Send error message as TextPart
|
|
1576
|
+
error_text = f"[Template rendering error: {str(e)}]"
|
|
1577
|
+
new_parts.append(a2a.create_text_part(text=error_text))
|
|
1578
|
+
|
|
1579
|
+
elif (
|
|
1580
|
+
data_type == "artifact_creation_progress"
|
|
1581
|
+
and not self.supports_inline_artifact_resolution
|
|
1582
|
+
):
|
|
1583
|
+
# Legacy gateway mode: convert completed artifact creation to FilePart
|
|
1584
|
+
status = part.data.get("status")
|
|
1585
|
+
if status == "completed" and not is_finalizing_context:
|
|
1586
|
+
# Extract artifact info from the DataPart
|
|
1587
|
+
filename = part.data.get("filename")
|
|
1588
|
+
version = part.data.get("version")
|
|
1589
|
+
mime_type = part.data.get("mime_type")
|
|
1590
|
+
|
|
1591
|
+
if filename and version is not None:
|
|
1592
|
+
log.info(
|
|
1593
|
+
"%s Converting artifact creation completion to FilePart for legacy gateway: %s v%s",
|
|
777
1594
|
log_id_prefix,
|
|
778
|
-
|
|
779
|
-
|
|
1595
|
+
filename,
|
|
1596
|
+
version,
|
|
780
1597
|
)
|
|
1598
|
+
# This will be sent as an artifact signal, so don't add to new_parts
|
|
1599
|
+
# Instead, add to other_signals for processing
|
|
1600
|
+
signal_tuple = (
|
|
1601
|
+
None,
|
|
1602
|
+
"SIGNAL_ARTIFACT_CREATION_COMPLETE",
|
|
1603
|
+
{
|
|
1604
|
+
"filename": filename,
|
|
1605
|
+
"version": version,
|
|
1606
|
+
"mime_type": mime_type,
|
|
1607
|
+
},
|
|
1608
|
+
)
|
|
1609
|
+
other_signals.append(signal_tuple)
|
|
1610
|
+
else:
|
|
1611
|
+
# Missing required info, keep the DataPart as-is
|
|
781
1612
|
new_parts.append(part)
|
|
1613
|
+
elif status == "completed" and is_finalizing_context:
|
|
1614
|
+
# Suppress during finalizing to avoid duplicates
|
|
1615
|
+
log.debug(
|
|
1616
|
+
"%s Suppressing artifact creation completion during finalizing context for %s",
|
|
1617
|
+
log_id_prefix,
|
|
1618
|
+
part.data.get("filename"),
|
|
1619
|
+
)
|
|
1620
|
+
continue
|
|
782
1621
|
else:
|
|
783
|
-
#
|
|
1622
|
+
# Keep in-progress or failed status DataParts
|
|
784
1623
|
new_parts.append(part)
|
|
785
1624
|
else:
|
|
1625
|
+
# Not an artifact creation DataPart, or modern gateway - keep as-is
|
|
786
1626
|
new_parts.append(part)
|
|
1627
|
+
else:
|
|
1628
|
+
new_parts.append(part)
|
|
787
1629
|
|
|
1630
|
+
if other_signals:
|
|
1631
|
+
await self._handle_resolved_signals(
|
|
1632
|
+
external_request_context,
|
|
1633
|
+
other_signals,
|
|
1634
|
+
original_rpc_id,
|
|
1635
|
+
is_finalizing_context,
|
|
1636
|
+
)
|
|
1637
|
+
|
|
1638
|
+
if new_parts != original_parts:
|
|
1639
|
+
content_modified = True
|
|
788
1640
|
if isinstance(parts_owner, A2AMessage):
|
|
789
1641
|
if isinstance(event_with_parts, TaskStatusUpdateEvent):
|
|
790
1642
|
event_with_parts.status.message = a2a.update_message_parts(
|
|
791
|
-
|
|
1643
|
+
parts_owner, new_parts
|
|
792
1644
|
)
|
|
793
1645
|
elif isinstance(event_with_parts, Task):
|
|
794
1646
|
event_with_parts.status.message = a2a.update_message_parts(
|
|
795
|
-
|
|
1647
|
+
parts_owner, new_parts
|
|
796
1648
|
)
|
|
797
1649
|
elif isinstance(parts_owner, A2AArtifact):
|
|
798
1650
|
event_with_parts.artifact = a2a.update_artifact_parts(
|
|
799
|
-
|
|
1651
|
+
parts_owner, new_parts
|
|
800
1652
|
)
|
|
801
1653
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
stream_buffer_key, current_buffer
|
|
805
|
-
)
|
|
1654
|
+
if is_streaming_status_update:
|
|
1655
|
+
self.task_context_manager.store_context(stream_buffer_key, current_buffer)
|
|
806
1656
|
|
|
807
|
-
return
|
|
1657
|
+
return content_modified or bool(other_signals)
|
|
808
1658
|
|
|
809
1659
|
async def _process_parsed_a2a_event(
|
|
810
1660
|
self,
|
|
@@ -837,13 +1687,6 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
837
1687
|
elif isinstance(parsed_event, Task):
|
|
838
1688
|
is_finalizing_context_for_embeds = True
|
|
839
1689
|
|
|
840
|
-
if self.resolve_artifact_uris_in_gateway:
|
|
841
|
-
log.debug(
|
|
842
|
-
"%s Resolving artifact URIs before sending to external...",
|
|
843
|
-
log_id_prefix,
|
|
844
|
-
)
|
|
845
|
-
await self._resolve_uris_in_payload(parsed_event)
|
|
846
|
-
|
|
847
1690
|
if not isinstance(parsed_event, JSONRPCError):
|
|
848
1691
|
content_was_modified_or_signals_handled = (
|
|
849
1692
|
await self._resolve_embeds_and_handle_signals(
|
|
@@ -855,6 +1698,15 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
855
1698
|
)
|
|
856
1699
|
)
|
|
857
1700
|
|
|
1701
|
+
if self.resolve_artifact_uris_in_gateway:
|
|
1702
|
+
log.debug(
|
|
1703
|
+
"%s Resolving artifact URIs before sending to external...",
|
|
1704
|
+
log_id_prefix,
|
|
1705
|
+
)
|
|
1706
|
+
await self._resolve_uris_in_payload(
|
|
1707
|
+
parsed_event, external_request_context
|
|
1708
|
+
)
|
|
1709
|
+
|
|
858
1710
|
send_this_event_to_external = True
|
|
859
1711
|
is_final_chunk_of_status_update = False
|
|
860
1712
|
|
|
@@ -925,6 +1777,7 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
925
1777
|
context=embed_eval_context,
|
|
926
1778
|
resolver_func=evaluate_embed,
|
|
927
1779
|
types_to_resolve=all_embed_types,
|
|
1780
|
+
resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
|
|
928
1781
|
log_identifier=log_id_prefix,
|
|
929
1782
|
config=embed_eval_config,
|
|
930
1783
|
)
|
|
@@ -986,12 +1839,13 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
986
1839
|
}
|
|
987
1840
|
resolved_remaining_text, _, signals = (
|
|
988
1841
|
await resolve_embeds_in_string(
|
|
989
|
-
remaining_buffer,
|
|
990
|
-
embed_eval_context,
|
|
991
|
-
evaluate_embed,
|
|
992
|
-
LATE_EMBED_TYPES.copy(),
|
|
993
|
-
|
|
994
|
-
|
|
1842
|
+
text=remaining_buffer,
|
|
1843
|
+
context=embed_eval_context,
|
|
1844
|
+
resolver_func=evaluate_embed,
|
|
1845
|
+
types_to_resolve=LATE_EMBED_TYPES.copy(),
|
|
1846
|
+
resolution_mode=ResolutionMode.A2A_MESSAGE_TO_USER,
|
|
1847
|
+
log_identifier=log_id_prefix,
|
|
1848
|
+
config=embed_eval_config,
|
|
995
1849
|
)
|
|
996
1850
|
)
|
|
997
1851
|
await self._handle_resolved_signals(
|
|
@@ -1017,6 +1871,32 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
1017
1871
|
|
|
1018
1872
|
if send_this_event_to_external:
|
|
1019
1873
|
if isinstance(parsed_event, Task):
|
|
1874
|
+
# Filter DataParts from final Task if gateway has filtering enabled
|
|
1875
|
+
# This prevents tool results and other internal data from appearing in user-facing output
|
|
1876
|
+
if (
|
|
1877
|
+
self.filter_tool_data_parts
|
|
1878
|
+
and parsed_event.status
|
|
1879
|
+
and parsed_event.status.message
|
|
1880
|
+
and parsed_event.status.message.parts
|
|
1881
|
+
):
|
|
1882
|
+
original_parts = a2a.get_parts_from_message(
|
|
1883
|
+
parsed_event.status.message
|
|
1884
|
+
)
|
|
1885
|
+
filtered_parts = [
|
|
1886
|
+
part
|
|
1887
|
+
for part in original_parts
|
|
1888
|
+
if self._should_include_data_part_in_final_output(part)
|
|
1889
|
+
]
|
|
1890
|
+
if len(filtered_parts) != len(original_parts):
|
|
1891
|
+
log.debug(
|
|
1892
|
+
"%s Filtered %d DataParts from final Task message",
|
|
1893
|
+
log_id_prefix,
|
|
1894
|
+
len(original_parts) - len(filtered_parts),
|
|
1895
|
+
)
|
|
1896
|
+
parsed_event.status.message = a2a.update_message_parts(
|
|
1897
|
+
parsed_event.status.message, filtered_parts
|
|
1898
|
+
)
|
|
1899
|
+
|
|
1020
1900
|
await self._send_final_response_to_external(
|
|
1021
1901
|
external_request_context, parsed_event
|
|
1022
1902
|
)
|
|
@@ -1166,10 +2046,6 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
1166
2046
|
)
|
|
1167
2047
|
self._start_listener()
|
|
1168
2048
|
|
|
1169
|
-
log.info(
|
|
1170
|
-
"%s Starting _message_processor_loop as an asyncio task.",
|
|
1171
|
-
self.log_identifier,
|
|
1172
|
-
)
|
|
1173
2049
|
await self._message_processor_loop()
|
|
1174
2050
|
|
|
1175
2051
|
def _pre_async_cleanup(self) -> None:
|
|
@@ -1197,7 +2073,7 @@ class BaseGatewayComponent(SamComponentBase):
|
|
|
1197
2073
|
self.internal_event_queue.put(None)
|
|
1198
2074
|
|
|
1199
2075
|
async def _message_processor_loop(self):
|
|
1200
|
-
log.
|
|
2076
|
+
log.debug("%s Starting message processor loop as an asyncio task...", self.log_identifier)
|
|
1201
2077
|
loop = self.get_async_loop()
|
|
1202
2078
|
|
|
1203
2079
|
while not self.stop_signal.is_set():
|