solace-agent-mesh 1.1.0__py3-none-any.whl → 1.3.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.

Files changed (168) hide show
  1. solace_agent_mesh/agent/adk/runner.py +18 -12
  2. solace_agent_mesh/agent/adk/services.py +3 -3
  3. solace_agent_mesh/agent/adk/setup.py +141 -34
  4. solace_agent_mesh/agent/protocol/event_handlers.py +27 -21
  5. solace_agent_mesh/agent/sac/app.py +0 -1
  6. solace_agent_mesh/agent/sac/component.py +0 -1
  7. solace_agent_mesh/agent/tools/__init__.py +1 -0
  8. solace_agent_mesh/agent/tools/dynamic_tool.py +362 -0
  9. solace_agent_mesh/assets/docs/404.html +3 -3
  10. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.d97b8e94.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/483cef9a.4e972867.js +1 -0
  12. solace_agent_mesh/assets/docs/assets/js/55f47984.cf3781c4.js +1 -0
  13. solace_agent_mesh/assets/docs/assets/js/664b740a.1b744a32.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/75384d09.c193a8f0.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/9a09e75d.d6607c56.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/aba87c2f.071e2d94.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/ae0e903d.4d8dda10.js +1 -0
  18. solace_agent_mesh/assets/docs/assets/js/c835a94d.146e3186.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/f284c35a.7334119c.js +1 -0
  20. solace_agent_mesh/assets/docs/assets/js/main.1c79039d.js +2 -0
  21. solace_agent_mesh/assets/docs/assets/js/runtime~main.858117b7.js +1 -0
  22. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +29 -0
  23. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +25 -0
  24. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html } +6 -6
  25. solace_agent_mesh/assets/docs/docs/documentation/{migration-guides/a2a-upgrade-to-0.3.0/a2a-technical-migration-map/index.html → Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html } +6 -6
  26. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +19 -27
  27. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +4 -4
  28. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +4 -4
  29. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +4 -4
  30. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +4 -4
  31. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +4 -4
  32. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  33. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +4 -4
  34. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +4 -4
  35. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +4 -4
  36. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +4 -4
  37. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +4 -4
  38. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +4 -4
  39. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +4 -4
  40. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +4 -4
  42. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +4 -4
  43. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  44. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +4 -4
  45. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +4 -4
  46. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +4 -4
  47. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +4 -4
  48. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +4 -4
  49. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +4 -4
  50. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +4 -4
  52. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +4 -4
  53. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +4 -4
  54. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +5 -4
  55. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +4 -4
  56. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +63 -0
  57. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +4 -4
  58. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +4 -4
  59. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  60. solace_agent_mesh/assets/docs/lunr-index-1757531604543.json +1 -0
  61. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  62. solace_agent_mesh/assets/docs/search-doc-1757531604543.json +1 -0
  63. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  64. solace_agent_mesh/assets/docs/sitemap.xml +1 -1
  65. solace_agent_mesh/cli/__init__.py +1 -1
  66. solace_agent_mesh/cli/commands/add_cmd/agent_cmd.py +125 -48
  67. solace_agent_mesh/cli/commands/eval_cmd.py +14 -0
  68. solace_agent_mesh/cli/commands/init_cmd/__init__.py +53 -31
  69. solace_agent_mesh/cli/commands/init_cmd/database_step.py +91 -0
  70. solace_agent_mesh/cli/commands/init_cmd/env_step.py +19 -8
  71. solace_agent_mesh/cli/commands/init_cmd/orchestrator_step.py +80 -25
  72. solace_agent_mesh/cli/commands/init_cmd/web_init_step.py +32 -10
  73. solace_agent_mesh/cli/commands/init_cmd/webui_gateway_step.py +74 -15
  74. solace_agent_mesh/cli/commands/plugin_cmd/create_cmd.py +0 -2
  75. solace_agent_mesh/cli/commands/run_cmd.py +5 -3
  76. solace_agent_mesh/cli/utils.py +68 -12
  77. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-CAX9u8a7.js +1 -0
  78. solace_agent_mesh/client/webui/frontend/static/assets/client-DXU9SPI5.js +25 -0
  79. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +1 -0
  80. solace_agent_mesh/client/webui/frontend/static/assets/main-C1k9E0aC.js +339 -0
  81. solace_agent_mesh/client/webui/frontend/static/assets/vendor-B0BEKoAR.js +390 -0
  82. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -2
  83. solace_agent_mesh/client/webui/frontend/static/index.html +4 -3
  84. solace_agent_mesh/common/utils/embeds/resolver.py +1 -0
  85. solace_agent_mesh/config_portal/backend/common.py +2 -2
  86. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-bFMKlzKf.js +98 -0
  87. solace_agent_mesh/config_portal/frontend/static/client/assets/{manifest-d845808d.js → manifest-89db7c30.js} +1 -1
  88. solace_agent_mesh/config_portal/frontend/static/client/index.html +1 -1
  89. solace_agent_mesh/evaluation/message_organizer.py +35 -56
  90. solace_agent_mesh/evaluation/run.py +26 -5
  91. solace_agent_mesh/evaluation/subscriber.py +35 -10
  92. solace_agent_mesh/evaluation/summary_builder.py +27 -34
  93. solace_agent_mesh/gateway/http_sse/ARCHITECTURE_GUIDE.md +676 -0
  94. solace_agent_mesh/gateway/http_sse/alembic/env.py +85 -0
  95. solace_agent_mesh/gateway/http_sse/alembic/script.py.mako +28 -0
  96. solace_agent_mesh/gateway/http_sse/alembic/versions/b1c2d3e4f5g6_add_database_indexes.py +83 -0
  97. solace_agent_mesh/gateway/http_sse/alembic/versions/d5b3f8f2e9a0_create_initial_database.py +58 -0
  98. solace_agent_mesh/gateway/http_sse/alembic.ini +147 -0
  99. solace_agent_mesh/gateway/http_sse/api/__init__.py +11 -0
  100. solace_agent_mesh/gateway/http_sse/api/controllers/__init__.py +9 -0
  101. solace_agent_mesh/gateway/http_sse/api/controllers/session_controller.py +355 -0
  102. solace_agent_mesh/gateway/http_sse/api/controllers/task_controller.py +279 -0
  103. solace_agent_mesh/gateway/http_sse/api/controllers/user_controller.py +35 -0
  104. solace_agent_mesh/gateway/http_sse/api/dto/__init__.py +10 -0
  105. solace_agent_mesh/gateway/http_sse/api/dto/requests/__init__.py +37 -0
  106. solace_agent_mesh/gateway/http_sse/api/dto/requests/session_requests.py +49 -0
  107. solace_agent_mesh/gateway/http_sse/api/dto/requests/task_requests.py +66 -0
  108. solace_agent_mesh/gateway/http_sse/api/dto/responses/__init__.py +43 -0
  109. solace_agent_mesh/gateway/http_sse/api/dto/responses/session_responses.py +68 -0
  110. solace_agent_mesh/gateway/http_sse/api/dto/responses/task_responses.py +74 -0
  111. solace_agent_mesh/gateway/http_sse/app.py +31 -1
  112. solace_agent_mesh/gateway/http_sse/application/__init__.py +3 -0
  113. solace_agent_mesh/gateway/http_sse/application/services/__init__.py +3 -0
  114. solace_agent_mesh/gateway/http_sse/application/services/session_service.py +135 -0
  115. solace_agent_mesh/gateway/http_sse/component.py +224 -62
  116. solace_agent_mesh/gateway/http_sse/dependencies.py +148 -45
  117. solace_agent_mesh/gateway/http_sse/domain/entities/__init__.py +3 -0
  118. solace_agent_mesh/gateway/http_sse/domain/entities/session.py +90 -0
  119. solace_agent_mesh/gateway/http_sse/domain/repositories/__init__.py +3 -0
  120. solace_agent_mesh/gateway/http_sse/domain/repositories/session_repository.py +54 -0
  121. solace_agent_mesh/gateway/http_sse/infrastructure/__init__.py +4 -0
  122. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/__init__.py +3 -0
  123. solace_agent_mesh/gateway/http_sse/infrastructure/dependency_injection/container.py +123 -0
  124. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/__init__.py +4 -0
  125. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_persistence_service.py +16 -0
  126. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/database_service.py +119 -0
  127. solace_agent_mesh/gateway/http_sse/infrastructure/persistence/models.py +31 -0
  128. solace_agent_mesh/gateway/http_sse/infrastructure/persistence_service.py +12 -0
  129. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/__init__.py +3 -0
  130. solace_agent_mesh/gateway/http_sse/infrastructure/repositories/session_repository.py +174 -0
  131. solace_agent_mesh/gateway/http_sse/main.py +291 -87
  132. solace_agent_mesh/gateway/http_sse/routers/{agents.py → agent_cards.py} +7 -7
  133. solace_agent_mesh/gateway/http_sse/routers/artifacts.py +121 -54
  134. solace_agent_mesh/gateway/http_sse/routers/config.py +3 -1
  135. solace_agent_mesh/gateway/http_sse/routers/tasks.py +83 -2
  136. solace_agent_mesh/gateway/http_sse/routers/visualization.py +7 -7
  137. solace_agent_mesh/gateway/http_sse/services/{agent_service.py → agent_card_service.py} +19 -19
  138. solace_agent_mesh/gateway/http_sse/session_manager.py +64 -30
  139. solace_agent_mesh/gateway/http_sse/shared/__init__.py +9 -0
  140. solace_agent_mesh/gateway/http_sse/shared/auth_utils.py +29 -0
  141. solace_agent_mesh/gateway/http_sse/shared/enums.py +45 -0
  142. solace_agent_mesh/gateway/http_sse/shared/types.py +45 -0
  143. solace_agent_mesh/templates/shared_config.yaml +4 -5
  144. solace_agent_mesh/templates/webui.yaml +8 -10
  145. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/METADATA +5 -3
  146. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/RECORD +150 -104
  147. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.8ccb9901.js +0 -1
  148. solace_agent_mesh/assets/docs/assets/js/55f47984.c484bf96.js +0 -1
  149. solace_agent_mesh/assets/docs/assets/js/6e0db977.39a79ca9.js +0 -1
  150. solace_agent_mesh/assets/docs/assets/js/75384d09.bf78fbdb.js +0 -1
  151. solace_agent_mesh/assets/docs/assets/js/90dd9cf6.88f385ea.js +0 -1
  152. solace_agent_mesh/assets/docs/assets/js/aba87c2f.76376d7c.js +0 -1
  153. solace_agent_mesh/assets/docs/assets/js/f284c35a.fb68323a.js +0 -1
  154. solace_agent_mesh/assets/docs/assets/js/main.a75ecc0d.js +0 -2
  155. solace_agent_mesh/assets/docs/assets/js/runtime~main.458efb1d.js +0 -1
  156. solace_agent_mesh/assets/docs/lunr-index-1756992446316.json +0 -1
  157. solace_agent_mesh/assets/docs/search-doc-1756992446316.json +0 -1
  158. solace_agent_mesh/client/webui/frontend/static/assets/authCallback-BmF2l6vg.js +0 -1
  159. solace_agent_mesh/client/webui/frontend/static/assets/client-D881Dttc.js +0 -49
  160. solace_agent_mesh/client/webui/frontend/static/assets/main-C0jZjYa8.js +0 -699
  161. solace_agent_mesh/client/webui/frontend/static/assets/main-CCeG324-.css +0 -1
  162. solace_agent_mesh/config_portal/frontend/static/client/assets/_index-Bym6YkMd.js +0 -98
  163. solace_agent_mesh/gateway/http_sse/routers/sessions.py +0 -85
  164. solace_agent_mesh/gateway/http_sse/routers/users.py +0 -59
  165. /solace_agent_mesh/assets/docs/assets/js/{main.a75ecc0d.js.LICENSE.txt → main.1c79039d.js.LICENSE.txt} +0 -0
  166. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/WHEEL +0 -0
  167. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/entry_points.txt +0 -0
  168. {solace_agent_mesh-1.1.0.dist-info → solace_agent_mesh-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -73,18 +73,21 @@ async def run_adk_async_task_thread_wrapper(
73
73
 
74
74
  if adk_session and component.session_service:
75
75
  context_setting_invocation_id = logical_task_id
76
- context_setting_event = ADKEvent(
77
- invocation_id=context_setting_invocation_id,
78
- author="A2A_Host_System",
79
- content=adk_types.Content(
80
- parts=[
81
- adk_types.Part(text="Initializing A2A context for task run.")
82
- ]
83
- ),
84
- actions=EventActions(state_delta={"a2a_context": a2a_context}),
85
- branch=None,
86
- )
76
+ original_message = a2a_context.pop("original_solace_message", None)
87
77
  try:
78
+ context_setting_event = ADKEvent(
79
+ invocation_id=context_setting_invocation_id,
80
+ author="A2A_Host_System",
81
+ content=adk_types.Content(
82
+ parts=[
83
+ adk_types.Part(
84
+ text="Initializing A2A context for task run."
85
+ )
86
+ ]
87
+ ),
88
+ actions=EventActions(state_delta={"a2a_context": a2a_context}),
89
+ branch=None,
90
+ )
88
91
  await component.session_service.append_event(
89
92
  session=adk_session, event=context_setting_event
90
93
  )
@@ -96,12 +99,15 @@ async def run_adk_async_task_thread_wrapper(
96
99
  )
97
100
  except Exception as e_append:
98
101
  log.error(
99
- "%s Failed to append context-setting event for task %s: %s. Tool scope filtering might not work if state is not persisted.",
102
+ "%s Failed to append context-setting event for task %s: %s.",
100
103
  component.log_identifier,
101
104
  logical_task_id,
102
105
  e_append,
103
106
  exc_info=True,
104
107
  )
108
+ finally:
109
+ if original_message:
110
+ a2a_context["original_solace_message"] = original_message
105
111
  else:
106
112
  log.warning(
107
113
  "%s Could not inject a2a_context into ADK session state via event for task %s (session or session_service invalid). Tool scope filtering might not work.",
@@ -174,11 +174,11 @@ def initialize_session_service(component) -> BaseSessionService:
174
174
 
175
175
  if service_type == "memory":
176
176
  return InMemorySessionService()
177
- elif service_type == "database":
178
- db_url = config.get("db_url")
177
+ elif service_type == "sql":
178
+ db_url = config.get("database_url")
179
179
  if not db_url:
180
180
  raise ValueError(
181
- f"{component.log_identifier} 'db_url' is required for database session service."
181
+ f"{component.log_identifier} 'database_url' is required for sql session service."
182
182
  )
183
183
  try:
184
184
  return DatabaseSessionService(db_url=db_url)
@@ -18,7 +18,6 @@ from google.adk import tools as adk_tools_module
18
18
  from google.adk.agents.callback_context import CallbackContext
19
19
  from google.adk.models.llm_request import LlmRequest
20
20
  from google.adk.models.llm_response import LlmResponse
21
- from google.adk.tools.mcp_tool import MCPToolset
22
21
  from google.adk.tools.mcp_tool.mcp_session_manager import (
23
22
  SseServerParams,
24
23
  StdioConnectionParams,
@@ -28,12 +27,49 @@ from mcp import StdioServerParameters
28
27
 
29
28
  from ..tools.registry import tool_registry
30
29
  from ..tools.tool_definition import BuiltinTool
30
+ from ..tools.dynamic_tool import DynamicTool, DynamicToolProvider
31
31
 
32
32
 
33
33
  from ...agent.adk import callbacks as adk_callbacks
34
34
  from ...agent.adk.models.lite_llm import LiteLlm
35
35
 
36
36
 
37
+ def _find_dynamic_tool_class(module) -> Optional[type]:
38
+ """Finds a single non-abstract DynamicTool subclass in a module."""
39
+ found_classes = []
40
+ for name, obj in inspect.getmembers(module, inspect.isclass):
41
+ if (
42
+ issubclass(obj, DynamicTool)
43
+ and obj is not DynamicTool
44
+ and not inspect.isabstract(obj)
45
+ ):
46
+ found_classes.append(obj)
47
+ if len(found_classes) > 1:
48
+ raise TypeError(
49
+ f"Module '{module.__name__}' contains multiple DynamicTool subclasses. "
50
+ "Please specify which one to use with 'class_name' in the config."
51
+ )
52
+ return found_classes[0] if found_classes else None
53
+
54
+
55
+ def _find_dynamic_tool_provider_class(module) -> Optional[type]:
56
+ """Finds a single non-abstract DynamicToolProvider subclass in a module."""
57
+ found_classes = []
58
+ for name, obj in inspect.getmembers(module, inspect.isclass):
59
+ if (
60
+ issubclass(obj, DynamicToolProvider)
61
+ and obj is not DynamicToolProvider
62
+ and not inspect.isabstract(obj)
63
+ ):
64
+ found_classes.append(obj)
65
+ if len(found_classes) > 1:
66
+ raise TypeError(
67
+ f"Module '{module.__name__}' contains multiple DynamicToolProvider subclasses. "
68
+ "Only one is permitted per module."
69
+ )
70
+ return found_classes[0] if found_classes else None
71
+
72
+
37
73
  async def load_adk_tools(
38
74
  component,
39
75
  ) -> Tuple[List[Union[BaseTool, Callable]], List[BuiltinTool]]:
@@ -90,48 +126,119 @@ async def load_adk_tools(
90
126
  try:
91
127
  if tool_type == "python":
92
128
  module_name = tool_config.get("component_module")
93
- function_name = tool_config.get("function_name")
94
- tool_name = tool_config.get("tool_name")
95
- tool_description = tool_config.get("tool_description")
96
129
  base_path = tool_config.get("component_base_path")
97
- if not module_name or not function_name:
130
+ if not module_name:
98
131
  raise ValueError(
99
- "'component_module' and 'function_name' required for python tool."
132
+ "'component_module' is required for python tools."
100
133
  )
101
-
102
134
  module = import_module(module_name, base_path=base_path)
103
- func = getattr(module, function_name)
104
- if not callable(func):
105
- raise TypeError(
106
- f"'{function_name}' in module '{module_name}' is not callable."
135
+
136
+ # Case 1: Simple function-based tool
137
+ if "function_name" in tool_config:
138
+ function_name = tool_config.get("function_name")
139
+ tool_name = tool_config.get("tool_name")
140
+ tool_description = tool_config.get("tool_description")
141
+
142
+ func = getattr(module, function_name)
143
+ if not callable(func):
144
+ raise TypeError(
145
+ f"'{function_name}' in module '{module_name}' is not callable."
146
+ )
147
+
148
+ specific_tool_config = tool_config.get("tool_config")
149
+ tool_callable = ADKToolWrapper(
150
+ func,
151
+ specific_tool_config,
152
+ function_name,
153
+ origin="python",
154
+ raw_string_args=tool_config.get("raw_string_args", []),
107
155
  )
108
156
 
109
- specific_tool_config = tool_config.get("tool_config")
110
- tool_callable = ADKToolWrapper(
111
- func,
112
- specific_tool_config,
113
- function_name,
114
- origin="python",
115
- raw_string_args=tool_config.get("raw_string_args", []),
116
- )
157
+ if tool_name:
158
+ function_name = tool_name
159
+ tool_callable.__name__ = tool_name
160
+
161
+ if tool_description:
162
+ tool_callable.__doc__ = tool_description
163
+
164
+ _check_and_register_tool_name(
165
+ function_name, f"python:{module_name}"
166
+ )
167
+ loaded_tools.append(tool_callable)
168
+ log.info(
169
+ "%s Loaded Python tool: %s from %s.",
170
+ component.log_identifier,
171
+ function_name,
172
+ module_name,
173
+ )
174
+ # Case 2: Advanced class-based dynamic tool or provider
175
+ else:
176
+ specific_tool_config = tool_config.get("tool_config")
177
+ dynamic_tools = []
117
178
 
118
- if tool_name:
119
- function_name = tool_name
120
- tool_callable.__name__ = tool_name
179
+ # Determine the class to load
180
+ tool_class = None
181
+ class_name = tool_config.get("class_name")
182
+ if class_name:
183
+ tool_class = getattr(module, class_name)
184
+ else:
185
+ # Auto-discover: provider first, then single tool
186
+ tool_class = _find_dynamic_tool_provider_class(module)
187
+ if not tool_class:
188
+ tool_class = _find_dynamic_tool_class(module)
189
+
190
+ if not tool_class:
191
+ raise TypeError(
192
+ f"Module '{module_name}' does not contain a 'function_name' or 'class_name' to load, "
193
+ "and no DynamicTool or DynamicToolProvider subclass could be auto-discovered."
194
+ )
121
195
 
122
- if tool_description:
123
- tool_callable.__doc__ = tool_description
196
+ # Instantiate tools from the class
197
+ if issubclass(tool_class, DynamicToolProvider):
198
+ provider_instance = tool_class()
199
+ dynamic_tools = (
200
+ provider_instance.get_all_tools_for_framework(
201
+ tool_config=specific_tool_config
202
+ )
203
+ )
204
+ log.info(
205
+ "%s Loaded %d tools from DynamicToolProvider '%s' in %s",
206
+ component.log_identifier,
207
+ len(dynamic_tools),
208
+ tool_class.__name__,
209
+ module_name,
210
+ )
211
+ elif issubclass(tool_class, DynamicTool):
212
+ tool_instance = tool_class(tool_config=specific_tool_config)
213
+ dynamic_tools = [tool_instance]
214
+ else:
215
+ raise TypeError(
216
+ f"Class '{tool_class.__name__}' in module '{module_name}' is not a valid "
217
+ "DynamicTool or DynamicToolProvider subclass."
218
+ )
124
219
 
125
- _check_and_register_tool_name(
126
- function_name, f"python:{module_name}"
127
- )
128
- loaded_tools.append(tool_callable)
129
- log.info(
130
- "%s Loaded Python tool: %s from %s.",
131
- component.log_identifier,
132
- function_name,
133
- module_name,
134
- )
220
+ # Process all generated tools
221
+ for tool in dynamic_tools:
222
+ tool.origin = "dynamic"
223
+ declaration = tool._get_declaration()
224
+ if not declaration:
225
+ log.warning(
226
+ "Dynamic tool '%s' from module '%s' did not generate a valid declaration. Skipping.",
227
+ tool.__class__.__name__,
228
+ module_name,
229
+ )
230
+ continue
231
+
232
+ _check_and_register_tool_name(
233
+ declaration.name, f"dynamic:{module_name}"
234
+ )
235
+ loaded_tools.append(tool)
236
+ log.info(
237
+ "%s Loaded dynamic tool: %s from %s",
238
+ component.log_identifier,
239
+ declaration.name,
240
+ module_name,
241
+ )
135
242
 
136
243
  elif tool_type == "builtin":
137
244
  tool_name = tool_config.get("tool_name")
@@ -229,27 +229,6 @@ async def handle_a2a_request(component, message: SolaceMessage):
229
229
  component.log_identifier,
230
230
  message.get_topic(),
231
231
  )
232
- a2a_context = {}
233
- adk_session = None
234
- jsonrpc_request_id = None
235
- logical_task_id = None
236
- client_id = message.get_user_properties().get("clientId", "default_client")
237
- status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
238
- reply_topic_from_peer = message.get_user_properties().get("replyTo")
239
- namespace = component.get_config("namespace")
240
- a2a_user_config = message.get_user_properties().get("a2aUserConfig", {})
241
- if not isinstance(a2a_user_config, dict):
242
- log.warning(
243
- "%s 'a2aUserConfig' user property is not a dictionary, received: %s. Defaulting to empty dict.",
244
- component.log_identifier,
245
- type(a2a_user_config),
246
- )
247
- a2a_user_config = {}
248
- log.debug(
249
- "%s Extracted 'a2aUserConfig': %s",
250
- component.log_identifier,
251
- a2a_user_config,
252
- )
253
232
  try:
254
233
  payload_dict = message.get_payload()
255
234
  if not isinstance(payload_dict, dict):
@@ -257,6 +236,16 @@ async def handle_a2a_request(component, message: SolaceMessage):
257
236
  a2a_request: A2ARequest = A2ARequest.model_validate(payload_dict)
258
237
  jsonrpc_request_id = a2a.get_request_id(a2a_request)
259
238
 
239
+ # Extract properties from message user properties
240
+ client_id = message.get_user_properties().get("clientId", "default_client")
241
+ status_topic_from_peer = message.get_user_properties().get("a2aStatusTopic")
242
+ reply_topic_from_peer = message.get_user_properties().get("replyTo")
243
+ namespace = component.get_config("namespace")
244
+ a2a_user_config = message.get_user_properties().get("a2aUserConfig", {})
245
+ if not isinstance(a2a_user_config, dict):
246
+ log.warning("a2aUserConfig is not a dict, using empty dict instead")
247
+ a2a_user_config = {}
248
+
260
249
  # The concept of logical_task_id changes. For Cancel, it's in params.id.
261
250
  # For Send, we will generate it.
262
251
  logical_task_id = None
@@ -411,6 +400,20 @@ async def handle_a2a_request(component, message: SolaceMessage):
411
400
  effective_session_id = original_session_id
412
401
  is_run_based_session = False
413
402
  temporary_run_session_id_for_cleanup = None
403
+
404
+ session_id_from_data = None
405
+ if a2a_message and a2a_message.parts:
406
+ for part in a2a_message.parts:
407
+ if isinstance(part, DataPart) and "session_id" in part.data:
408
+ session_id_from_data = part.data["session_id"]
409
+ log.info(
410
+ f"Extracted session_id '{session_id_from_data}' from DataPart."
411
+ )
412
+ break
413
+
414
+ if session_id_from_data:
415
+ original_session_id = session_id_from_data
416
+
414
417
  if session_behavior == "RUN_BASED":
415
418
  is_run_based_session = True
416
419
  effective_session_id = f"{original_session_id}:{logical_task_id}:run"
@@ -432,6 +435,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
432
435
  effective_session_id,
433
436
  logical_task_id,
434
437
  )
438
+
435
439
  adk_session_for_run = await component.session_service.get_session(
436
440
  app_name=agent_name, user_id=user_id, session_id=effective_session_id
437
441
  )
@@ -447,6 +451,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
447
451
  effective_session_id,
448
452
  logical_task_id,
449
453
  )
454
+
450
455
  else:
451
456
  log.info(
452
457
  "%s Reusing existing ADK session '%s' for task '%s'.",
@@ -454,6 +459,7 @@ async def handle_a2a_request(component, message: SolaceMessage):
454
459
  effective_session_id,
455
460
  logical_task_id,
456
461
  )
462
+
457
463
  if is_run_based_session:
458
464
  try:
459
465
  original_adk_session_data = (
@@ -14,7 +14,6 @@ patch_adk()
14
14
 
15
15
  from typing import Any, Dict
16
16
  from solace_ai_connector.flow.app import App
17
- from solace_ai_connector.common.log import log
18
17
 
19
18
  from ...common.a2a import (
20
19
  get_agent_request_topic,
@@ -12,7 +12,6 @@ import fnmatch
12
12
  import base64
13
13
  from datetime import datetime, timezone
14
14
  import json
15
- import json
16
15
  from solace_ai_connector.common.message import (
17
16
  Message as SolaceMessage,
18
17
  )
@@ -12,3 +12,4 @@ from . import audio_tools
12
12
  from . import image_tools
13
13
  from . import web_tools
14
14
  from . import test_tools
15
+ from . import dynamic_tool