solace-agent-mesh 1.3.2__py3-none-any.whl → 1.4.0__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 (136) hide show
  1. solace_agent_mesh/agent/adk/artifacts/filesystem_artifact_service.py +16 -8
  2. solace_agent_mesh/agent/adk/setup.py +183 -8
  3. solace_agent_mesh/agent/sac/app.py +337 -622
  4. solace_agent_mesh/agent/sac/component.py +47 -1
  5. solace_agent_mesh/agent/tools/dynamic_tool.py +36 -5
  6. solace_agent_mesh/agent/tools/tool_config_types.py +58 -0
  7. solace_agent_mesh/assets/docs/404.html +3 -3
  8. solace_agent_mesh/assets/docs/assets/js/0e682baa.da822665.js +1 -0
  9. solace_agent_mesh/assets/docs/assets/js/1023fc19.8a8a9309.js +1 -0
  10. solace_agent_mesh/assets/docs/assets/js/1523c6b4.2645ef68.js +1 -0
  11. solace_agent_mesh/assets/docs/assets/js/1c6e87d2.43771adc.js +1 -0
  12. solace_agent_mesh/assets/docs/assets/js/2a9cab12.2afaee76.js +1 -0
  13. solace_agent_mesh/assets/docs/assets/js/332e10b5.f7629851.js +1 -0
  14. solace_agent_mesh/assets/docs/assets/js/3d406171.5560fdf9.js +1 -0
  15. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.508ae8db.js +1 -0
  16. solace_agent_mesh/assets/docs/assets/js/442a8107.b5c2532a.js +1 -0
  17. solace_agent_mesh/assets/docs/assets/js/483cef9a.8d318c2f.js +1 -0
  18. solace_agent_mesh/assets/docs/assets/js/55f47984.bcd00a86.js +1 -0
  19. solace_agent_mesh/assets/docs/assets/js/5b4258a4.dff11eca.js +1 -0
  20. solace_agent_mesh/assets/docs/assets/js/664b740a.ba305a89.js +1 -0
  21. solace_agent_mesh/assets/docs/assets/js/75384d09.abdf9cf9.js +1 -0
  22. solace_agent_mesh/assets/docs/assets/js/768e31b0.9abcdc48.js +1 -0
  23. solace_agent_mesh/assets/docs/assets/js/945fb41e.abf2be91.js +1 -0
  24. solace_agent_mesh/assets/docs/assets/js/9a09e75d.92de8cf5.js +1 -0
  25. solace_agent_mesh/assets/docs/assets/js/9eff14a2.d62aad71.js +1 -0
  26. solace_agent_mesh/assets/docs/assets/js/a3a92b25.1d029b81.js +1 -0
  27. solace_agent_mesh/assets/docs/assets/js/{aba87c2f.071e2d94.js → aba87c2f.4ddf32f2.js} +1 -1
  28. solace_agent_mesh/assets/docs/assets/js/ae0e903d.abca774a.js +1 -0
  29. solace_agent_mesh/assets/docs/assets/js/ae4415af.24cdc514.js +1 -0
  30. solace_agent_mesh/assets/docs/assets/js/bac0be12.27ee2c26.js +1 -0
  31. solace_agent_mesh/assets/docs/assets/js/c2c06897.87cb1f47.js +1 -0
  32. solace_agent_mesh/assets/docs/assets/js/c835a94d.ce21f0bf.js +1 -0
  33. solace_agent_mesh/assets/docs/assets/js/cc969b05.feef7dcc.js +1 -0
  34. solace_agent_mesh/assets/docs/assets/js/cd3d4052.a19e7d78.js +1 -0
  35. solace_agent_mesh/assets/docs/assets/js/{cee5d587.f5b73ca1.js → cee5d587.f1e1ca86.js} +1 -1
  36. solace_agent_mesh/assets/docs/assets/js/f284c35a.cad4dbf2.js +1 -0
  37. solace_agent_mesh/assets/docs/assets/js/f897a61a.bc634a3e.js +1 -0
  38. solace_agent_mesh/assets/docs/assets/js/{main.4adc477a.js → main.1de3da6a.js} +2 -2
  39. solace_agent_mesh/assets/docs/assets/js/runtime~main.3188e049.js +1 -0
  40. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/installation/index.html +4 -4
  41. solace_agent_mesh/assets/docs/docs/documentation/Enterprise/single-sign-on/index.html +5 -5
  42. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-gateway-upgrade-to-0.3.0/index.html +6 -6
  43. solace_agent_mesh/assets/docs/docs/documentation/Migrations/A2A Upgrade To 0.3.0/a2a-technical-migration-map/index.html +6 -6
  44. solace_agent_mesh/assets/docs/docs/documentation/concepts/agents/index.html +6 -6
  45. solace_agent_mesh/assets/docs/docs/documentation/concepts/architecture/index.html +3 -3
  46. solace_agent_mesh/assets/docs/docs/documentation/concepts/cli/index.html +18 -18
  47. solace_agent_mesh/assets/docs/docs/documentation/concepts/gateways/index.html +3 -3
  48. solace_agent_mesh/assets/docs/docs/documentation/concepts/orchestrator/index.html +3 -3
  49. solace_agent_mesh/assets/docs/docs/documentation/concepts/plugins/index.html +10 -10
  50. solace_agent_mesh/assets/docs/docs/documentation/deployment/debugging/index.html +4 -4
  51. solace_agent_mesh/assets/docs/docs/documentation/deployment/deploy/index.html +3 -3
  52. solace_agent_mesh/assets/docs/docs/documentation/deployment/observability/index.html +3 -3
  53. solace_agent_mesh/assets/docs/docs/documentation/getting-started/component-overview/index.html +7 -7
  54. solace_agent_mesh/assets/docs/docs/documentation/getting-started/configurations/index.html +3 -3
  55. solace_agent_mesh/assets/docs/docs/documentation/getting-started/installation/index.html +9 -9
  56. solace_agent_mesh/assets/docs/docs/documentation/getting-started/introduction/index.html +6 -6
  57. solace_agent_mesh/assets/docs/docs/documentation/getting-started/quick-start/index.html +7 -7
  58. solace_agent_mesh/assets/docs/docs/documentation/tutorials/bedrock-agents/index.html +23 -23
  59. solace_agent_mesh/assets/docs/docs/documentation/tutorials/custom-agent/index.html +5 -5
  60. solace_agent_mesh/assets/docs/docs/documentation/tutorials/event-mesh-gateway/index.html +5 -5
  61. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mcp-integration/index.html +4 -4
  62. solace_agent_mesh/assets/docs/docs/documentation/tutorials/mongodb-integration/index.html +9 -9
  63. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rag-integration/index.html +10 -10
  64. solace_agent_mesh/assets/docs/docs/documentation/tutorials/rest-gateway/index.html +8 -8
  65. solace_agent_mesh/assets/docs/docs/documentation/tutorials/slack-integration/index.html +6 -6
  66. solace_agent_mesh/assets/docs/docs/documentation/tutorials/sql-database/index.html +9 -9
  67. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/artifact-management/index.html +3 -3
  68. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/audio-tools/index.html +5 -5
  69. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/data-analysis-tools/index.html +5 -5
  70. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/embeds/index.html +3 -3
  71. solace_agent_mesh/assets/docs/docs/documentation/user-guide/builtin-tools/index.html +5 -5
  72. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-agents/index.html +19 -19
  73. solace_agent_mesh/assets/docs/docs/documentation/user-guide/create-gateways/index.html +7 -7
  74. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-python-tools/index.html +73 -8
  75. solace_agent_mesh/assets/docs/docs/documentation/user-guide/creating-service-providers/index.html +10 -10
  76. solace_agent_mesh/assets/docs/docs/documentation/user-guide/solace-ai-connector/index.html +3 -3
  77. solace_agent_mesh/assets/docs/docs/documentation/user-guide/structure/index.html +4 -4
  78. solace_agent_mesh/assets/docs/lunr-index-1757991496554.json +1 -0
  79. solace_agent_mesh/assets/docs/lunr-index.json +1 -1
  80. solace_agent_mesh/assets/docs/search-doc-1757991496554.json +1 -0
  81. solace_agent_mesh/assets/docs/search-doc.json +1 -1
  82. solace_agent_mesh/cli/__init__.py +1 -1
  83. solace_agent_mesh/cli/commands/run_cmd.py +4 -7
  84. solace_agent_mesh/client/webui/frontend/static/assets/{authCallback-CAX9u8a7.js → authCallback-j1LW-wlq.js} +1 -1
  85. solace_agent_mesh/client/webui/frontend/static/assets/{client-DXU9SPI5.js → client-B9p_nFNA.js} +1 -1
  86. solace_agent_mesh/client/webui/frontend/static/assets/main-B9s_V9tJ.css +1 -0
  87. solace_agent_mesh/client/webui/frontend/static/assets/main-Dq4AJNvn.js +339 -0
  88. solace_agent_mesh/client/webui/frontend/static/assets/{vendor-B0BEKoAR.js → vendor-CS5YMf8a.js} +74 -69
  89. solace_agent_mesh/client/webui/frontend/static/auth-callback.html +3 -3
  90. solace_agent_mesh/client/webui/frontend/static/index.html +4 -4
  91. solace_agent_mesh/common/utils/pydantic_utils.py +56 -0
  92. solace_agent_mesh/config_portal/backend/plugin_catalog/registry_manager.py +6 -4
  93. solace_agent_mesh/gateway/base/app.py +58 -120
  94. solace_agent_mesh/gateway/http_sse/app.py +99 -150
  95. solace_agent_mesh/gateway/http_sse/component.py +57 -30
  96. solace_agent_mesh/gateway/http_sse/sse_event_buffer.py +87 -0
  97. solace_agent_mesh/gateway/http_sse/sse_manager.py +44 -23
  98. {solace_agent_mesh-1.3.2.dist-info → solace_agent_mesh-1.4.0.dist-info}/METADATA +1 -1
  99. {solace_agent_mesh-1.3.2.dist-info → solace_agent_mesh-1.4.0.dist-info}/RECORD +103 -100
  100. solace_agent_mesh/assets/docs/assets/js/0e682baa.b3bbde9a.js +0 -1
  101. solace_agent_mesh/assets/docs/assets/js/1023fc19.364235d5.js +0 -1
  102. solace_agent_mesh/assets/docs/assets/js/1523c6b4.1b0ec6f9.js +0 -1
  103. solace_agent_mesh/assets/docs/assets/js/1c6e87d2.a8c5ce5a.js +0 -1
  104. solace_agent_mesh/assets/docs/assets/js/2a9cab12.8909df92.js +0 -1
  105. solace_agent_mesh/assets/docs/assets/js/332e10b5.7a103f42.js +0 -1
  106. solace_agent_mesh/assets/docs/assets/js/3d406171.0b9eeed1.js +0 -1
  107. solace_agent_mesh/assets/docs/assets/js/42b3f8d8.d97b8e94.js +0 -1
  108. solace_agent_mesh/assets/docs/assets/js/442a8107.b3159bb2.js +0 -1
  109. solace_agent_mesh/assets/docs/assets/js/483cef9a.03d5dceb.js +0 -1
  110. solace_agent_mesh/assets/docs/assets/js/55f47984.cf3781c4.js +0 -1
  111. solace_agent_mesh/assets/docs/assets/js/5b4258a4.0d080cd9.js +0 -1
  112. solace_agent_mesh/assets/docs/assets/js/664b740a.1b744a32.js +0 -1
  113. solace_agent_mesh/assets/docs/assets/js/75384d09.c193a8f0.js +0 -1
  114. solace_agent_mesh/assets/docs/assets/js/768e31b0.8b51cd70.js +0 -1
  115. solace_agent_mesh/assets/docs/assets/js/945fb41e.c63791d1.js +0 -1
  116. solace_agent_mesh/assets/docs/assets/js/9a09e75d.d6607c56.js +0 -1
  117. solace_agent_mesh/assets/docs/assets/js/9eff14a2.472b0310.js +0 -1
  118. solace_agent_mesh/assets/docs/assets/js/a3a92b25.4b7fa6a2.js +0 -1
  119. solace_agent_mesh/assets/docs/assets/js/ae0e903d.4d8dda10.js +0 -1
  120. solace_agent_mesh/assets/docs/assets/js/ae4415af.7a2f0bbf.js +0 -1
  121. solace_agent_mesh/assets/docs/assets/js/bac0be12.f50d9bac.js +0 -1
  122. solace_agent_mesh/assets/docs/assets/js/c2c06897.587b4af5.js +0 -1
  123. solace_agent_mesh/assets/docs/assets/js/c835a94d.146e3186.js +0 -1
  124. solace_agent_mesh/assets/docs/assets/js/cc969b05.bd3e0d6c.js +0 -1
  125. solace_agent_mesh/assets/docs/assets/js/cd3d4052.b6535013.js +0 -1
  126. solace_agent_mesh/assets/docs/assets/js/f284c35a.7334119c.js +0 -1
  127. solace_agent_mesh/assets/docs/assets/js/f897a61a.0aa29dbb.js +0 -1
  128. solace_agent_mesh/assets/docs/assets/js/runtime~main.cf0229ea.js +0 -1
  129. solace_agent_mesh/assets/docs/lunr-index-1757704179464.json +0 -1
  130. solace_agent_mesh/assets/docs/search-doc-1757704179464.json +0 -1
  131. solace_agent_mesh/client/webui/frontend/static/assets/main-C03yrETa.css +0 -1
  132. solace_agent_mesh/client/webui/frontend/static/assets/main-DjoMeldu.js +0 -339
  133. /solace_agent_mesh/assets/docs/assets/js/{main.4adc477a.js.LICENSE.txt → main.1de3da6a.js.LICENSE.txt} +0 -0
  134. {solace_agent_mesh-1.3.2.dist-info → solace_agent_mesh-1.4.0.dist-info}/WHEEL +0 -0
  135. {solace_agent_mesh-1.3.2.dist-info → solace_agent_mesh-1.4.0.dist-info}/entry_points.txt +0 -0
  136. {solace_agent_mesh-1.3.2.dist-info → solace_agent_mesh-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -140,21 +140,29 @@ class FilesystemArtifactService(BaseArtifactService):
140
140
  if not artifact.inline_data or artifact.inline_data.data is None:
141
141
  raise ValueError("Artifact Part has no inline_data to save.")
142
142
 
143
+ metadata = {"mime_type": artifact.inline_data.mime_type}
144
+
143
145
  def _write_data_file():
146
+ """Write artifact data and fsync to disk."""
144
147
  with open(version_path, "wb") as f:
145
148
  f.write(artifact.inline_data.data)
146
-
147
- await asyncio.to_thread(_write_data_file)
148
- logger.debug("%sWrote data to %s", log_prefix, version_path)
149
-
150
- metadata = {"mime_type": artifact.inline_data.mime_type}
149
+ f.flush()
150
+ os.fsync(f.fileno())
151
+ logger.debug("%sWrote data to %s", log_prefix, version_path)
151
152
 
152
153
  def _write_metadata_file():
154
+ """Write artifact metadata and fsync to disk."""
153
155
  with open(metadata_path, "w", encoding="utf-8") as f:
154
156
  json.dump(metadata, f)
155
-
156
- await asyncio.to_thread(_write_metadata_file)
157
- logger.debug("%sWrote metadata to %s", log_prefix, metadata_path)
157
+ f.flush()
158
+ os.fsync(f.fileno())
159
+ logger.debug("%sWrote metadata to %s", log_prefix, metadata_path)
160
+
161
+ # Run file writes concurrently and wait for both to complete
162
+ await asyncio.gather(
163
+ asyncio.to_thread(_write_data_file),
164
+ asyncio.to_thread(_write_metadata_file),
165
+ )
158
166
 
159
167
  logger.info(
160
168
  "%sSaved artifact '%s' version %d successfully.",
@@ -2,7 +2,7 @@
2
2
  Handles ADK Agent and Runner initialization, including tool loading and callback assignment.
3
3
  """
4
4
 
5
- from typing import Dict, List, Optional, Union, Callable, Tuple, Set
5
+ from typing import Dict, List, Optional, Union, Callable, Tuple, Set, Any, TYPE_CHECKING, Type
6
6
  import functools
7
7
  import inspect
8
8
  from solace_ai_connector.common.log import log
@@ -25,9 +25,13 @@ from google.adk.tools.mcp_tool.mcp_session_manager import (
25
25
 
26
26
  from mcp import StdioServerParameters
27
27
 
28
+ if TYPE_CHECKING:
29
+ from ..sac.component import SamAgentComponent
30
+
28
31
  from ..tools.registry import tool_registry
29
32
  from ..tools.tool_definition import BuiltinTool
30
33
  from ..tools.dynamic_tool import DynamicTool, DynamicToolProvider
34
+ from ..tools.tool_config_types import AnyToolConfig
31
35
 
32
36
 
33
37
  from ...agent.adk import callbacks as adk_callbacks
@@ -52,6 +56,83 @@ def _find_dynamic_tool_class(module) -> Optional[type]:
52
56
  return found_classes[0] if found_classes else None
53
57
 
54
58
 
59
+ async def _execute_lifecycle_hook(
60
+ component: "SamAgentComponent",
61
+ func_name: Optional[str],
62
+ module_name: str,
63
+ base_path: Optional[str],
64
+ tool_config_model: AnyToolConfig,
65
+ ):
66
+ """Dynamically loads and executes a lifecycle hook function."""
67
+ if not func_name:
68
+ return
69
+
70
+ log.info(
71
+ "%s Executing lifecycle hook: %s.%s",
72
+ component.log_identifier,
73
+ module_name,
74
+ func_name,
75
+ )
76
+
77
+ try:
78
+ module = import_module(module_name, base_path=base_path)
79
+ func = getattr(module, func_name)
80
+
81
+ if not inspect.iscoroutinefunction(func):
82
+ raise TypeError(
83
+ f"Lifecycle hook '{func_name}' in module '{module_name}' must be an async function."
84
+ )
85
+
86
+ await func(component, tool_config_model)
87
+ log.info(
88
+ "%s Successfully executed lifecycle hook: %s.%s",
89
+ component.log_identifier,
90
+ module_name,
91
+ func_name,
92
+ )
93
+ except Exception as e:
94
+ log.exception(
95
+ "%s Fatal error during lifecycle hook execution for '%s.%s': %s",
96
+ component.log_identifier,
97
+ module_name,
98
+ func_name,
99
+ e,
100
+ )
101
+ raise RuntimeError(f"Tool lifecycle initialization failed: {e}") from e
102
+
103
+
104
+ def _create_cleanup_partial(
105
+ component: "SamAgentComponent",
106
+ func_name: Optional[str],
107
+ module_name: str,
108
+ base_path: Optional[str],
109
+ tool_config_model: AnyToolConfig,
110
+ ) -> Optional[Callable]:
111
+ """Creates a functools.partial for a cleanup hook function."""
112
+ if not func_name:
113
+ return None
114
+
115
+ try:
116
+ module = import_module(module_name, base_path=base_path)
117
+ func = getattr(module, func_name)
118
+
119
+ if not inspect.iscoroutinefunction(func):
120
+ raise TypeError(
121
+ f"Lifecycle hook '{func_name}' in module '{module_name}' must be an async function."
122
+ )
123
+
124
+ return functools.partial(func, component, tool_config_model)
125
+ except Exception as e:
126
+ log.exception(
127
+ "%s Fatal error creating partial for cleanup hook '%s.%s': %s",
128
+ component.log_identifier,
129
+ module_name,
130
+ func_name,
131
+ e,
132
+ )
133
+ raise RuntimeError(f"Tool lifecycle setup failed: {e}") from e
134
+
135
+
55
136
  def _find_dynamic_tool_provider_class(module) -> Optional[type]:
56
137
  """Finds a single non-abstract DynamicToolProvider subclass in a module."""
57
138
  found_classes = []
@@ -72,7 +153,7 @@ def _find_dynamic_tool_provider_class(module) -> Optional[type]:
72
153
 
73
154
  async def load_adk_tools(
74
155
  component,
75
- ) -> Tuple[List[Union[BaseTool, Callable]], List[BuiltinTool]]:
156
+ ) -> Tuple[List[Union[BaseTool, Callable]], List[BuiltinTool], List[Callable]]:
76
157
  """
77
158
  Loads all configured tools for the agent.
78
159
  - Explicitly configured tools (Python, MCP, ADK Built-ins) from YAML.
@@ -86,6 +167,7 @@ async def load_adk_tools(
86
167
  A tuple containing:
87
168
  - A list of loaded tool callables/instances for the ADK agent.
88
169
  - A list of enabled BuiltinTool definition objects for prompt generation.
170
+ - A list of awaitable cleanup functions for the tools.
89
171
 
90
172
  Raises:
91
173
  ImportError: If a configured tool or its dependencies cannot be loaded.
@@ -93,8 +175,13 @@ async def load_adk_tools(
93
175
  loaded_tools: List[Union[BaseTool, Callable]] = []
94
176
  enabled_builtin_tools: List[BuiltinTool] = []
95
177
  loaded_tool_names: Set[str] = set()
178
+ cleanup_hooks: List[Callable] = []
96
179
  tools_config = component.get_config("tools", [])
97
180
 
181
+ from pydantic import TypeAdapter, BaseModel, ValidationError
182
+
183
+ any_tool_adapter = TypeAdapter(AnyToolConfig)
184
+
98
185
  def _check_and_register_tool_name(name: str, source: str):
99
186
  """Checks for duplicate tool names and raises ValueError if found."""
100
187
  if name in loaded_tool_names:
@@ -121,9 +208,11 @@ async def load_adk_tools(
121
208
  component.log_identifier,
122
209
  )
123
210
  for tool_config in tools_config:
124
- tool_type = tool_config.get("tool_type", "").lower()
125
-
211
+ newly_loaded_tools = []
126
212
  try:
213
+ tool_config_model = any_tool_adapter.validate_python(tool_config)
214
+ tool_type = tool_config_model.tool_type.lower()
215
+
127
216
  if tool_type == "python":
128
217
  module_name = tool_config.get("component_module")
129
218
  base_path = tool_config.get("component_base_path")
@@ -165,6 +254,7 @@ async def load_adk_tools(
165
254
  function_name, f"python:{module_name}"
166
255
  )
167
256
  loaded_tools.append(tool_callable)
257
+ newly_loaded_tools.append(tool_callable)
168
258
  log.info(
169
259
  "%s Loaded Python tool: %s from %s.",
170
260
  component.log_identifier,
@@ -193,12 +283,44 @@ async def load_adk_tools(
193
283
  "and no DynamicTool or DynamicToolProvider subclass could be auto-discovered."
194
284
  )
195
285
 
286
+ # Check for a Pydantic model declaration on the tool class
287
+ config_model: Optional[Type["BaseModel"]] = getattr(
288
+ tool_class, "config_model", None
289
+ )
290
+ validated_config: Union[dict, "BaseModel"] = specific_tool_config
291
+
292
+ if config_model:
293
+ log.debug(
294
+ "%s Found config_model '%s' for tool class '%s'. Validating...",
295
+ component.log_identifier,
296
+ config_model.__name__,
297
+ tool_class.__name__,
298
+ )
299
+ try:
300
+ # Validate the raw dict and get a Pydantic model instance
301
+ validated_config = config_model.model_validate(
302
+ specific_tool_config or {}
303
+ )
304
+ log.debug(
305
+ "%s Successfully validated tool_config for '%s'.",
306
+ component.log_identifier,
307
+ tool_class.__name__,
308
+ )
309
+ except ValidationError as e:
310
+ # Provide a clear error message and raise
311
+ error_msg = (
312
+ f"Configuration error for tool '{tool_class.__name__}' from module '{module_name}'. "
313
+ f"The provided 'tool_config' in your YAML is invalid:\n{e}"
314
+ )
315
+ log.error("%s %s", component.log_identifier, error_msg)
316
+ raise ValueError(error_msg) from e
317
+
196
318
  # Instantiate tools from the class
197
319
  if issubclass(tool_class, DynamicToolProvider):
198
320
  provider_instance = tool_class()
199
321
  dynamic_tools = (
200
322
  provider_instance.get_all_tools_for_framework(
201
- tool_config=specific_tool_config
323
+ tool_config=validated_config
202
324
  )
203
325
  )
204
326
  log.info(
@@ -209,7 +331,7 @@ async def load_adk_tools(
209
331
  module_name,
210
332
  )
211
333
  elif issubclass(tool_class, DynamicTool):
212
- tool_instance = tool_class(tool_config=specific_tool_config)
334
+ tool_instance = tool_class(tool_config=validated_config)
213
335
  dynamic_tools = [tool_instance]
214
336
  else:
215
337
  raise TypeError(
@@ -233,6 +355,7 @@ async def load_adk_tools(
233
355
  declaration.name, f"dynamic:{module_name}"
234
356
  )
235
357
  loaded_tools.append(tool)
358
+ newly_loaded_tools.append(tool)
236
359
  log.info(
237
360
  "%s Loaded dynamic tool: %s from %s",
238
361
  component.log_identifier,
@@ -240,6 +363,54 @@ async def load_adk_tools(
240
363
  module_name,
241
364
  )
242
365
 
366
+ # --- Lifecycle Hook Execution for Python Tools ---
367
+ module_name = tool_config_model.component_module
368
+ base_path = tool_config_model.component_base_path
369
+
370
+ # 1. YAML Init
371
+ await _execute_lifecycle_hook(
372
+ component,
373
+ tool_config_model.init_function,
374
+ module_name,
375
+ base_path,
376
+ tool_config_model,
377
+ )
378
+
379
+ # 2. DynamicTool Init
380
+ for tool_instance in newly_loaded_tools:
381
+ if isinstance(tool_instance, DynamicTool):
382
+ log.info(
383
+ "%s Executing .init() method for DynamicTool '%s'.",
384
+ component.log_identifier,
385
+ tool_instance.tool_name,
386
+ )
387
+ await tool_instance.init(component, tool_config_model)
388
+
389
+ # 3. Collect Cleanup Hooks (LIFO order)
390
+ yaml_cleanup_partial = _create_cleanup_partial(
391
+ component,
392
+ tool_config_model.cleanup_function,
393
+ module_name,
394
+ base_path,
395
+ tool_config_model,
396
+ )
397
+
398
+ dynamic_cleanup_partials = []
399
+ for tool_instance in newly_loaded_tools:
400
+ if isinstance(tool_instance, DynamicTool):
401
+ dynamic_cleanup_partials.append(
402
+ functools.partial(
403
+ tool_instance.cleanup, component, tool_config_model
404
+ )
405
+ )
406
+
407
+ # Add to main list, prepending to get LIFO execution
408
+ if yaml_cleanup_partial:
409
+ cleanup_hooks.insert(0, yaml_cleanup_partial)
410
+
411
+ for partial_func in reversed(dynamic_cleanup_partials):
412
+ cleanup_hooks.insert(0, partial_func)
413
+
243
414
  elif tool_type == "builtin":
244
415
  tool_name = tool_config.get("tool_name")
245
416
  if not tool_name:
@@ -258,6 +429,7 @@ async def load_adk_tools(
258
429
  raw_string_args=sam_tool_def.raw_string_args,
259
430
  )
260
431
  loaded_tools.append(tool_callable)
432
+ newly_loaded_tools.append(tool_callable)
261
433
  enabled_builtin_tools.append(sam_tool_def)
262
434
  log.info(
263
435
  "%s Loaded SAM built-in tool: %s",
@@ -342,6 +514,7 @@ async def load_adk_tools(
342
514
  raw_string_args=tool_def.raw_string_args,
343
515
  )
344
516
  loaded_tools.append(tool_callable)
517
+ newly_loaded_tools.append(tool_callable)
345
518
  enabled_builtin_tools.append(tool_def)
346
519
  group_tool_count += 1
347
520
  log.info(
@@ -460,6 +633,7 @@ async def load_adk_tools(
460
633
  raise
461
634
 
462
635
  loaded_tools.append(mcp_toolset_instance)
636
+ newly_loaded_tools.append(mcp_toolset_instance)
463
637
  log.info(
464
638
  "%s Initialized MCPToolset (filter: %s) for server: %s",
465
639
  component.log_identifier,
@@ -526,12 +700,13 @@ async def load_adk_tools(
526
700
  )
527
701
 
528
702
  log.info(
529
- "%s Finished loading tools. Total tools for ADK: %d. Total SAM built-ins for prompt: %d. Peer tools added dynamically.",
703
+ "%s Finished loading tools. Total tools for ADK: %d. Total SAM built-ins for prompt: %d. Total cleanup hooks: %d. Peer tools added dynamically.",
530
704
  component.log_identifier,
531
705
  len(loaded_tools),
532
706
  len(enabled_builtin_tools),
707
+ len(cleanup_hooks),
533
708
  )
534
- return loaded_tools, enabled_builtin_tools
709
+ return loaded_tools, enabled_builtin_tools, cleanup_hooks
535
710
 
536
711
 
537
712
  def initialize_adk_agent(