google-adk 1.1.1__py3-none-any.whl → 1.2.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.
Files changed (67) hide show
  1. google/adk/agents/base_agent.py +0 -2
  2. google/adk/agents/invocation_context.py +3 -3
  3. google/adk/agents/parallel_agent.py +17 -7
  4. google/adk/agents/sequential_agent.py +8 -8
  5. google/adk/auth/auth_preprocessor.py +18 -17
  6. google/adk/cli/agent_graph.py +165 -23
  7. google/adk/cli/browser/assets/ADK-512-color.svg +9 -0
  8. google/adk/cli/browser/index.html +2 -2
  9. google/adk/cli/browser/{main-PKDNKWJE.js → main-CS5OLUMF.js} +59 -59
  10. google/adk/cli/browser/polyfills-FFHMD2TL.js +17 -0
  11. google/adk/cli/cli.py +9 -9
  12. google/adk/cli/cli_deploy.py +157 -0
  13. google/adk/cli/cli_tools_click.py +228 -99
  14. google/adk/cli/fast_api.py +119 -34
  15. google/adk/cli/utils/agent_loader.py +60 -44
  16. google/adk/cli/utils/envs.py +1 -1
  17. google/adk/code_executors/unsafe_local_code_executor.py +11 -0
  18. google/adk/errors/__init__.py +13 -0
  19. google/adk/errors/not_found_error.py +28 -0
  20. google/adk/evaluation/agent_evaluator.py +1 -1
  21. google/adk/evaluation/eval_sets_manager.py +36 -6
  22. google/adk/evaluation/evaluation_generator.py +5 -4
  23. google/adk/evaluation/local_eval_sets_manager.py +101 -6
  24. google/adk/flows/llm_flows/agent_transfer.py +2 -2
  25. google/adk/flows/llm_flows/base_llm_flow.py +19 -0
  26. google/adk/flows/llm_flows/contents.py +4 -4
  27. google/adk/flows/llm_flows/functions.py +140 -127
  28. google/adk/memory/vertex_ai_rag_memory_service.py +2 -2
  29. google/adk/models/anthropic_llm.py +7 -10
  30. google/adk/models/google_llm.py +46 -18
  31. google/adk/models/lite_llm.py +63 -26
  32. google/adk/py.typed +0 -0
  33. google/adk/sessions/_session_util.py +10 -16
  34. google/adk/sessions/database_session_service.py +81 -66
  35. google/adk/sessions/vertex_ai_session_service.py +32 -6
  36. google/adk/telemetry.py +91 -24
  37. google/adk/tools/_automatic_function_calling_util.py +31 -25
  38. google/adk/tools/{function_parameter_parse_util.py → _function_parameter_parse_util.py} +9 -3
  39. google/adk/tools/_gemini_schema_util.py +158 -0
  40. google/adk/tools/apihub_tool/apihub_toolset.py +3 -2
  41. google/adk/tools/application_integration_tool/clients/connections_client.py +7 -0
  42. google/adk/tools/application_integration_tool/integration_connector_tool.py +5 -7
  43. google/adk/tools/base_tool.py +4 -8
  44. google/adk/tools/bigquery/bigquery_credentials.py +7 -3
  45. google/adk/tools/function_tool.py +4 -4
  46. google/adk/tools/langchain_tool.py +20 -13
  47. google/adk/tools/load_memory_tool.py +1 -0
  48. google/adk/tools/mcp_tool/conversion_utils.py +4 -2
  49. google/adk/tools/mcp_tool/mcp_session_manager.py +63 -5
  50. google/adk/tools/mcp_tool/mcp_tool.py +3 -2
  51. google/adk/tools/mcp_tool/mcp_toolset.py +15 -8
  52. google/adk/tools/openapi_tool/common/common.py +4 -43
  53. google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +0 -2
  54. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +4 -2
  55. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +4 -2
  56. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +7 -127
  57. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +2 -7
  58. google/adk/tools/transfer_to_agent_tool.py +8 -1
  59. google/adk/tools/vertex_ai_search_tool.py +8 -1
  60. google/adk/utils/variant_utils.py +51 -0
  61. google/adk/version.py +1 -1
  62. {google_adk-1.1.1.dist-info → google_adk-1.2.0.dist-info}/METADATA +7 -7
  63. {google_adk-1.1.1.dist-info → google_adk-1.2.0.dist-info}/RECORD +66 -60
  64. google/adk/cli/browser/polyfills-B6TNHZQ6.js +0 -17
  65. {google_adk-1.1.1.dist-info → google_adk-1.2.0.dist-info}/WHEEL +0 -0
  66. {google_adk-1.1.1.dist-info → google_adk-1.2.0.dist-info}/entry_points.txt +0 -0
  67. {google_adk-1.1.1.dist-info → google_adk-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,13 +12,15 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from typing import Any
15
+ from __future__ import annotations
16
+
16
17
  from typing import Optional
17
18
  from typing import Union
18
19
 
19
20
  from google.genai import types
20
21
  from langchain.agents import Tool
21
22
  from langchain_core.tools import BaseTool
23
+ from langchain_core.tools.structured import StructuredTool
22
24
  from typing_extensions import override
23
25
 
24
26
  from . import _automatic_function_calling_util
@@ -63,9 +65,13 @@ class LangchainTool(FunctionTool):
63
65
  raise ValueError("Langchain tool must have a 'run' or '_run' method")
64
66
 
65
67
  # Determine which function to use
66
- func = tool._run if hasattr(tool, '_run') else tool.run
68
+ if isinstance(tool, StructuredTool):
69
+ func = tool.func
70
+ else:
71
+ func = tool._run if hasattr(tool, '_run') else tool.run
67
72
  super().__init__(func)
68
-
73
+ # run_manager is a special parameter for langchain tool
74
+ self._ignore_params.append('run_manager')
69
75
  self._langchain_tool = tool
70
76
 
71
77
  # Set name: priority is 1) explicitly provided name, 2) tool's name, 3) default
@@ -112,20 +118,21 @@ class LangchainTool(FunctionTool):
112
118
  ):
113
119
  tool_wrapper.args_schema = self._langchain_tool.args_schema
114
120
 
115
- return _automatic_function_calling_util.build_function_declaration_for_langchain(
116
- False,
117
- self.name,
118
- self.description,
119
- tool_wrapper.func,
120
- getattr(tool_wrapper, 'args', None),
121
- )
121
+ return _automatic_function_calling_util.build_function_declaration_for_langchain(
122
+ False,
123
+ self.name,
124
+ self.description,
125
+ tool_wrapper.func,
126
+ tool_wrapper.args,
127
+ )
122
128
 
123
129
  # Need to provide a way to override the function names and descriptions
124
130
  # as the original function names are mostly ".run" and the descriptions
125
131
  # may not meet users' needs
126
- return _automatic_function_calling_util.build_function_declaration(
127
- func=self._langchain_tool.run,
128
- )
132
+ function_decl = super()._get_declaration()
133
+ function_decl.name = self.name
134
+ function_decl.description = self.description
135
+ return function_decl
129
136
 
130
137
  except Exception as e:
131
138
  raise ValueError(
@@ -69,6 +69,7 @@ class LoadMemoryTool(FunctionTool):
69
69
  type=types.Type.STRING,
70
70
  )
71
71
  },
72
+ required=['query'],
72
73
  ),
73
74
  )
74
75
 
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  from typing import Any
16
18
  from typing import Dict
17
19
 
@@ -41,10 +43,10 @@ def adk_to_mcp_tool_type(tool: BaseTool) -> mcp_types.Tool:
41
43
  print(mcp_tool)
42
44
  """
43
45
  tool_declaration = tool._get_declaration()
44
- if not tool_declaration:
46
+ if not tool_declaration or not tool_declaration.parameters:
45
47
  input_schema = {}
46
48
  else:
47
- input_schema = gemini_to_json_schema(tool._get_declaration().parameters)
49
+ input_schema = gemini_to_json_schema(tool_declaration.parameters)
48
50
  return mcp_types.Tool(
49
51
  name=tool.name,
50
52
  description=tool.description,
@@ -12,14 +12,17 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
15
16
 
16
17
  from contextlib import AsyncExitStack
18
+ from datetime import timedelta
17
19
  import functools
18
20
  import logging
19
21
  import sys
20
22
  from typing import Any
21
23
  from typing import Optional
22
24
  from typing import TextIO
25
+ from typing import Union
23
26
 
24
27
  import anyio
25
28
  from pydantic import BaseModel
@@ -29,6 +32,7 @@ try:
29
32
  from mcp import StdioServerParameters
30
33
  from mcp.client.sse import sse_client
31
34
  from mcp.client.stdio import stdio_client
35
+ from mcp.client.streamable_http import streamablehttp_client
32
36
  except ImportError as e:
33
37
  import sys
34
38
 
@@ -56,6 +60,20 @@ class SseServerParams(BaseModel):
56
60
  sse_read_timeout: float = 60 * 5
57
61
 
58
62
 
63
+ class StreamableHTTPServerParams(BaseModel):
64
+ """Parameters for the MCP SSE connection.
65
+
66
+ See MCP SSE Client documentation for more details.
67
+ https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/client/streamable_http.py
68
+ """
69
+
70
+ url: str
71
+ headers: dict[str, Any] | None = None
72
+ timeout: float = 5
73
+ sse_read_timeout: float = 60 * 5
74
+ terminate_on_close: bool = True
75
+
76
+
59
77
  def retry_on_closed_resource(async_reinit_func_name: str):
60
78
  """Decorator to automatically reinitialize session and retry action.
61
79
 
@@ -123,13 +141,17 @@ class MCPSessionManager:
123
141
 
124
142
  def __init__(
125
143
  self,
126
- connection_params: StdioServerParameters | SseServerParams,
144
+ connection_params: Union[
145
+ StdioServerParameters, SseServerParams, StreamableHTTPServerParams
146
+ ],
127
147
  errlog: TextIO = sys.stderr,
128
148
  ):
129
149
  """Initializes the MCP session manager.
130
150
 
131
151
  Args:
132
- connection_params: Parameters for the MCP connection (Stdio or SSE).
152
+ connection_params: Parameters for the MCP connection (Stdio, SSE or
153
+ Streamable HTTP). Stdio by default also has a 5s read timeout as other
154
+ parameters but it's not configurable for now.
133
155
  errlog: (Optional) TextIO stream for error logging. Use only for
134
156
  initializing a local stdio MCP session.
135
157
  """
@@ -153,6 +175,9 @@ class MCPSessionManager:
153
175
 
154
176
  try:
155
177
  if isinstance(self._connection_params, StdioServerParameters):
178
+ # So far timeout is not configurable. Given MCP is still evolving, we
179
+ # would expect stdio_client to evolve to accept timeout parameter like
180
+ # other client.
156
181
  client = stdio_client(
157
182
  server=self._connection_params, errlog=self._errlog
158
183
  )
@@ -163,6 +188,16 @@ class MCPSessionManager:
163
188
  timeout=self._connection_params.timeout,
164
189
  sse_read_timeout=self._connection_params.sse_read_timeout,
165
190
  )
191
+ elif isinstance(self._connection_params, StreamableHTTPServerParams):
192
+ client = streamablehttp_client(
193
+ url=self._connection_params.url,
194
+ headers=self._connection_params.headers,
195
+ timeout=timedelta(seconds=self._connection_params.timeout),
196
+ sse_read_timeout=timedelta(
197
+ seconds=self._connection_params.sse_read_timeout
198
+ ),
199
+ terminate_on_close=self._connection_params.terminate_on_close,
200
+ )
166
201
  else:
167
202
  raise ValueError(
168
203
  'Unable to initialize connection. Connection should be'
@@ -171,9 +206,32 @@ class MCPSessionManager:
171
206
  )
172
207
 
173
208
  transports = await self._exit_stack.enter_async_context(client)
174
- session = await self._exit_stack.enter_async_context(
175
- ClientSession(*transports)
176
- )
209
+ # The streamable http client returns a GetSessionCallback in addition to the read/write MemoryObjectStreams
210
+ # needed to build the ClientSession, we limit then to the two first values to be compatible with all clients.
211
+ # The StdioServerParameters does not provide a timeout parameter for the
212
+ # session, so we need to set a default timeout for it. Other clients
213
+ # (SseServerParams and StreamableHTTPServerParams) already provide a
214
+ # timeout parameter in their configuration.
215
+ if isinstance(self._connection_params, StdioServerParameters):
216
+ # Default timeout for MCP session is 5 seconds, same as SseServerParams
217
+ # and StreamableHTTPServerParams.
218
+ # TODO :
219
+ # 1. make timeout configurable
220
+ # 2. Add StdioConnectionParams to include StdioServerParameters as a
221
+ # field and rename other two params to XXXXConnetionParams. Ohter
222
+ # two params are actually connection params, while stdio is
223
+ # special, stdio_client takes the resposibility of starting the
224
+ # server and working as a client.
225
+ session = await self._exit_stack.enter_async_context(
226
+ ClientSession(
227
+ *transports[:2],
228
+ read_timeout_seconds=timedelta(seconds=5),
229
+ )
230
+ )
231
+ else:
232
+ session = await self._exit_stack.enter_async_context(
233
+ ClientSession(*transports[:2])
234
+ )
177
235
  await session.initialize()
178
236
 
179
237
  self._session = session
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
15
16
 
16
17
  import logging
17
18
  from typing import Optional
@@ -19,6 +20,7 @@ from typing import Optional
19
20
  from google.genai.types import FunctionDeclaration
20
21
  from typing_extensions import override
21
22
 
23
+ from .._gemini_schema_util import _to_gemini_schema
22
24
  from .mcp_session_manager import MCPSessionManager
23
25
  from .mcp_session_manager import retry_on_closed_resource
24
26
 
@@ -41,7 +43,6 @@ except ImportError as e:
41
43
  from ...auth.auth_credential import AuthCredential
42
44
  from ...auth.auth_schemes import AuthScheme
43
45
  from ..base_tool import BaseTool
44
- from ..openapi_tool.openapi_spec_parser.rest_api_tool import to_gemini_schema
45
46
  from ..tool_context import ToolContext
46
47
 
47
48
  logger = logging.getLogger("google_adk." + __name__)
@@ -98,7 +99,7 @@ class MCPTool(BaseTool):
98
99
  FunctionDeclaration: The Gemini function declaration for the tool.
99
100
  """
100
101
  schema_dict = self._mcp_tool.inputSchema
101
- parameters = to_gemini_schema(schema_dict)
102
+ parameters = _to_gemini_schema(schema_dict)
102
103
  function_decl = FunctionDeclaration(
103
104
  name=self.name, description=self.description, parameters=parameters
104
105
  )
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import logging
16
18
  import sys
17
19
  from typing import List
@@ -26,6 +28,7 @@ from ..base_toolset import ToolPredicate
26
28
  from .mcp_session_manager import MCPSessionManager
27
29
  from .mcp_session_manager import retry_on_closed_resource
28
30
  from .mcp_session_manager import SseServerParams
31
+ from .mcp_session_manager import StreamableHTTPServerParams
29
32
 
30
33
  # Attempt to import MCP Tool from the MCP library, and hints user to upgrade
31
34
  # their Python version to 3.10 if it fails.
@@ -82,20 +85,23 @@ class MCPToolset(BaseToolset):
82
85
  def __init__(
83
86
  self,
84
87
  *,
85
- connection_params: StdioServerParameters | SseServerParams,
88
+ connection_params: (
89
+ StdioServerParameters | SseServerParams | StreamableHTTPServerParams
90
+ ),
86
91
  tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
87
92
  errlog: TextIO = sys.stderr,
88
93
  ):
89
94
  """Initializes the MCPToolset.
90
95
 
91
96
  Args:
92
- connection_params: The connection parameters to the MCP server. Can be:
93
- `StdioServerParameters` for using local mcp server (e.g. using `npx` or
94
- `python3`); or `SseServerParams` for a local/remote SSE server.
95
- tool_filter: Optional filter to select specific tools. Can be either:
96
- - A list of tool names to include
97
- - A ToolPredicate function for custom filtering logic
98
- errlog: TextIO stream for error logging.
97
+ connection_params: The connection parameters to the MCP server. Can be:
98
+ `StdioServerParameters` for using local mcp server (e.g. using `npx` or
99
+ `python3`); or `SseServerParams` for a local/remote SSE server; or
100
+ `StreamableHTTPServerParams` for local/remote Streamable http server.
101
+ tool_filter: Optional filter to select specific tools. Can be either:
102
+ - A list of tool names to include
103
+ - A ToolPredicate function for custom filtering logic
104
+ errlog: TextIO stream for error logging.
99
105
  """
100
106
  super().__init__(tool_filter=tool_filter)
101
107
 
@@ -110,6 +116,7 @@ class MCPToolset(BaseToolset):
110
116
  connection_params=self._connection_params,
111
117
  errlog=self._errlog,
112
118
  )
119
+
113
120
  self._session = None
114
121
 
115
122
  @retry_on_closed_resource("_reinitialize_session")
@@ -12,8 +12,9 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import keyword
16
- import re
17
18
  from typing import Any
18
19
  from typing import Dict
19
20
  from typing import List
@@ -26,47 +27,7 @@ from pydantic import BaseModel
26
27
  from pydantic import Field
27
28
  from pydantic import model_serializer
28
29
 
29
-
30
- def to_snake_case(text: str) -> str:
31
- """Converts a string into snake_case.
32
-
33
- Handles lowerCamelCase, UpperCamelCase, or space-separated case, acronyms
34
- (e.g., "REST API") and consecutive uppercase letters correctly. Also handles
35
- mixed cases with and without spaces.
36
-
37
- Examples:
38
- ```
39
- to_snake_case('camelCase') -> 'camel_case'
40
- to_snake_case('UpperCamelCase') -> 'upper_camel_case'
41
- to_snake_case('space separated') -> 'space_separated'
42
- ```
43
-
44
- Args:
45
- text: The input string.
46
-
47
- Returns:
48
- The snake_case version of the string.
49
- """
50
-
51
- # Handle spaces and non-alphanumeric characters (replace with underscores)
52
- text = re.sub(r'[^a-zA-Z0-9]+', '_', text)
53
-
54
- # Insert underscores before uppercase letters (handling both CamelCases)
55
- text = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', text) # lowerCamelCase
56
- text = re.sub(
57
- r'([A-Z]+)([A-Z][a-z])', r'\1_\2', text
58
- ) # UpperCamelCase and acronyms
59
-
60
- # Convert to lowercase
61
- text = text.lower()
62
-
63
- # Remove consecutive underscores (clean up extra underscores)
64
- text = re.sub(r'_+', '_', text)
65
-
66
- # Remove leading and trailing underscores
67
- text = text.strip('_')
68
-
69
- return text
30
+ from ..._gemini_schema_util import _to_snake_case
70
31
 
71
32
 
72
33
  def rename_python_keywords(s: str, prefix: str = 'param_') -> str:
@@ -106,7 +67,7 @@ class ApiParameter(BaseModel):
106
67
  self.py_name = (
107
68
  self.py_name
108
69
  if self.py_name
109
- else rename_python_keywords(to_snake_case(self.original_name))
70
+ else rename_python_keywords(_to_snake_case(self.original_name))
110
71
  )
111
72
  if isinstance(self.param_schema, str):
112
73
  self.param_schema = Schema.model_validate_json(self.param_schema)
@@ -20,7 +20,6 @@ from .operation_parser import OperationParser
20
20
  from .rest_api_tool import AuthPreparationState
21
21
  from .rest_api_tool import RestApiTool
22
22
  from .rest_api_tool import snake_to_lower_camel
23
- from .rest_api_tool import to_gemini_schema
24
23
  from .tool_auth_handler import ToolAuthHandler
25
24
 
26
25
  __all__ = [
@@ -30,7 +29,6 @@ __all__ = [
30
29
  'OpenAPIToolset',
31
30
  'OperationParser',
32
31
  'RestApiTool',
33
- 'to_gemini_schema',
34
32
  'snake_to_lower_camel',
35
33
  'AuthPreparationState',
36
34
  'ToolAuthHandler',
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import copy
16
18
  from typing import Any
17
19
  from typing import Dict
@@ -23,8 +25,8 @@ from pydantic import BaseModel
23
25
 
24
26
  from ....auth.auth_credential import AuthCredential
25
27
  from ....auth.auth_schemes import AuthScheme
28
+ from ..._gemini_schema_util import _to_snake_case
26
29
  from ..common.common import ApiParameter
27
- from ..common.common import to_snake_case
28
30
  from .operation_parser import OperationParser
29
31
 
30
32
 
@@ -112,7 +114,7 @@ class OpenApiSpecParser:
112
114
  # If operation ID is missing, assign an operation id based on path
113
115
  # and method
114
116
  if "operationId" not in operation_dict:
115
- temp_id = to_snake_case(f"{path}_{method}")
117
+ temp_id = _to_snake_case(f"{path}_{method}")
116
118
  operation_dict["operationId"] = temp_id
117
119
 
118
120
  url = OperationEndpoint(base_url=base_url, path=path, method=method)
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import inspect
16
18
  from textwrap import dedent
17
19
  from typing import Any
@@ -25,9 +27,9 @@ from fastapi.openapi.models import Operation
25
27
  from fastapi.openapi.models import Parameter
26
28
  from fastapi.openapi.models import Schema
27
29
 
30
+ from ..._gemini_schema_util import _to_snake_case
28
31
  from ..common.common import ApiParameter
29
32
  from ..common.common import PydocHelper
30
- from ..common.common import to_snake_case
31
33
 
32
34
 
33
35
  class OperationParser:
@@ -189,7 +191,7 @@ class OperationParser:
189
191
  operation_id = self._operation.operationId
190
192
  if not operation_id:
191
193
  raise ValueError('Operation ID is missing')
192
- return to_snake_case(operation_id)[:60]
194
+ return _to_snake_case(operation_id)[:60]
193
195
 
194
196
  def get_return_type_hint(self) -> str:
195
197
  """Returns the return type hint string (like 'str', 'int', etc.)."""
@@ -12,45 +12,36 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  from typing import Any
16
18
  from typing import Dict
17
19
  from typing import List
18
20
  from typing import Literal
19
21
  from typing import Optional
20
- from typing import Sequence
21
22
  from typing import Tuple
22
23
  from typing import Union
23
24
 
24
25
  from fastapi.openapi.models import Operation
25
26
  from google.genai.types import FunctionDeclaration
26
- from google.genai.types import Schema
27
27
  import requests
28
28
  from typing_extensions import override
29
29
 
30
30
  from ....auth.auth_credential import AuthCredential
31
31
  from ....auth.auth_schemes import AuthScheme
32
- from ....tools.base_tool import BaseTool
32
+ from ..._gemini_schema_util import _to_gemini_schema
33
+ from ..._gemini_schema_util import _to_snake_case
34
+ from ...base_tool import BaseTool
33
35
  from ...tool_context import ToolContext
34
36
  from ..auth.auth_helpers import credential_to_param
35
37
  from ..auth.auth_helpers import dict_to_auth_scheme
36
38
  from ..auth.credential_exchangers.auto_auth_credential_exchanger import AutoAuthCredentialExchanger
37
39
  from ..common.common import ApiParameter
38
- from ..common.common import to_snake_case
39
40
  from .openapi_spec_parser import OperationEndpoint
40
41
  from .openapi_spec_parser import ParsedOperation
41
42
  from .operation_parser import OperationParser
42
43
  from .tool_auth_handler import ToolAuthHandler
43
44
 
44
- # Not supported by the Gemini API
45
- _OPENAPI_SCHEMA_IGNORE_FIELDS = (
46
- "title",
47
- "default",
48
- "format",
49
- "additional_properties",
50
- "ref",
51
- "def",
52
- )
53
-
54
45
 
55
46
  def snake_to_lower_camel(snake_case_string: str):
56
47
  """Converts a snake_case string to a lower_camel_case string.
@@ -70,117 +61,6 @@ def snake_to_lower_camel(snake_case_string: str):
70
61
  ])
71
62
 
72
63
 
73
- # TODO: Switch to Gemini `from_json_schema` util when it is released
74
- # in Gemini SDK.
75
- def normalize_json_schema_type(
76
- json_schema_type: Optional[Union[str, Sequence[str]]],
77
- ) -> tuple[Optional[str], bool]:
78
- """Converts a JSON Schema Type into Gemini Schema type.
79
-
80
- Adopted and modified from Gemini SDK. This gets the first available schema
81
- type from JSON Schema, and use it to mark Gemini schema type. If JSON Schema
82
- contains a list of types, the first non null type is used.
83
-
84
- Remove this after switching to Gemini `from_json_schema`.
85
- """
86
- if json_schema_type is None:
87
- return None, False
88
- if isinstance(json_schema_type, str):
89
- if json_schema_type == "null":
90
- return None, True
91
- return json_schema_type, False
92
-
93
- non_null_types = []
94
- nullable = False
95
- # If json schema type is an array, pick the first non null type.
96
- for type_value in json_schema_type:
97
- if type_value == "null":
98
- nullable = True
99
- else:
100
- non_null_types.append(type_value)
101
- non_null_type = non_null_types[0] if non_null_types else None
102
- return non_null_type, nullable
103
-
104
-
105
- # TODO: Switch to Gemini `from_json_schema` util when it is released
106
- # in Gemini SDK.
107
- def to_gemini_schema(openapi_schema: Optional[Dict[str, Any]] = None) -> Schema:
108
- """Converts an OpenAPI schema dictionary to a Gemini Schema object.
109
-
110
- Args:
111
- openapi_schema: The OpenAPI schema dictionary.
112
-
113
- Returns:
114
- A Pydantic Schema object. Returns None if input is None.
115
- Raises TypeError if input is not a dict.
116
- """
117
- if openapi_schema is None:
118
- return None
119
-
120
- if not isinstance(openapi_schema, dict):
121
- raise TypeError("openapi_schema must be a dictionary")
122
-
123
- pydantic_schema_data = {}
124
-
125
- # Adding this to force adding a type to an empty dict
126
- # This avoid "... one_of or any_of must specify a type" error
127
- if not openapi_schema.get("type"):
128
- openapi_schema["type"] = "object"
129
-
130
- for key, value in openapi_schema.items():
131
- snake_case_key = to_snake_case(key)
132
- # Check if the snake_case_key exists in the Schema model's fields.
133
- if snake_case_key in Schema.model_fields:
134
- if snake_case_key in _OPENAPI_SCHEMA_IGNORE_FIELDS:
135
- # Ignore these fields as Gemini backend doesn't recognize them, and will
136
- # throw exception if they appear in the schema.
137
- # Format: properties[expiration].format: only 'enum' and 'date-time' are
138
- # supported for STRING type
139
- continue
140
- elif snake_case_key == "type":
141
- schema_type, nullable = normalize_json_schema_type(
142
- openapi_schema.get("type", None)
143
- )
144
- # Adding this to force adding a type to an empty dict
145
- # This avoid "... one_of or any_of must specify a type" error
146
- pydantic_schema_data["type"] = schema_type if schema_type else "object"
147
- pydantic_schema_data["type"] = pydantic_schema_data["type"].upper()
148
- if nullable:
149
- pydantic_schema_data["nullable"] = True
150
- elif snake_case_key == "properties" and isinstance(value, dict):
151
- pydantic_schema_data[snake_case_key] = {
152
- k: to_gemini_schema(v) for k, v in value.items()
153
- }
154
- elif snake_case_key == "items" and isinstance(value, dict):
155
- pydantic_schema_data[snake_case_key] = to_gemini_schema(value)
156
- elif snake_case_key == "any_of" and isinstance(value, list):
157
- pydantic_schema_data[snake_case_key] = [
158
- to_gemini_schema(item) for item in value
159
- ]
160
- # Important: Handle cases where the OpenAPI schema might contain lists
161
- # or other structures that need to be recursively processed.
162
- elif isinstance(value, list) and snake_case_key not in (
163
- "enum",
164
- "required",
165
- "property_ordering",
166
- ):
167
- new_list = []
168
- for item in value:
169
- if isinstance(item, dict):
170
- new_list.append(to_gemini_schema(item))
171
- else:
172
- new_list.append(item)
173
- pydantic_schema_data[snake_case_key] = new_list
174
- elif isinstance(value, dict) and snake_case_key not in ("properties"):
175
- # Handle dictionary which is neither properties or items
176
- pydantic_schema_data[snake_case_key] = to_gemini_schema(value)
177
- else:
178
- # Simple value assignment (int, str, bool, etc.)
179
- pydantic_schema_data[snake_case_key] = value
180
-
181
- return Schema(**pydantic_schema_data)
182
-
183
-
184
64
  AuthPreparationState = Literal["pending", "done"]
185
65
 
186
66
 
@@ -273,7 +153,7 @@ class RestApiTool(BaseTool):
273
153
  parsed.operation, parsed.parameters, parsed.return_value
274
154
  )
275
155
 
276
- tool_name = to_snake_case(operation_parser.get_function_name())
156
+ tool_name = _to_snake_case(operation_parser.get_function_name())
277
157
  generated = cls(
278
158
  name=tool_name,
279
159
  description=parsed.operation.description
@@ -306,7 +186,7 @@ class RestApiTool(BaseTool):
306
186
  def _get_declaration(self) -> FunctionDeclaration:
307
187
  """Returns the function declaration in the Gemini Schema format."""
308
188
  schema_dict = self._operation_parser.get_json_schema()
309
- parameters = to_gemini_schema(schema_dict)
189
+ parameters = _to_gemini_schema(schema_dict)
310
190
  function_decl = FunctionDeclaration(
311
191
  name=self.name, description=self.description, parameters=parameters
312
192
  )
@@ -185,7 +185,7 @@ class ToolAuthHandler:
185
185
  )
186
186
  self.credential_store.store_credential(key, auth_credential)
187
187
 
188
- def _reqeust_credential(self) -> None:
188
+ def _request_credential(self) -> None:
189
189
  """Handles the case where an OpenID Connect or OAuth2 authentication request is needed."""
190
190
  if self.auth_scheme.type_ in (
191
191
  AuthSchemeType.openIdConnect,
@@ -223,11 +223,6 @@ class ToolAuthHandler:
223
223
  )
224
224
  )
225
225
 
226
- def _request_credential(self, auth_config: AuthConfig):
227
- if not self.tool_context:
228
- return
229
- self.tool_context.request_credential(auth_config)
230
-
231
226
  def prepare_auth_credentials(
232
227
  self,
233
228
  ) -> AuthPreparationResult:
@@ -260,7 +255,7 @@ class ToolAuthHandler:
260
255
  auth_credential=exchanged_credential,
261
256
  )
262
257
  else:
263
- self._reqeust_credential()
258
+ self._request_credential()
264
259
  return AuthPreparationResult(
265
260
  state="pending",
266
261
  auth_scheme=self.auth_scheme,