google-adk 1.4.0__py3-none-any.whl → 1.4.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.
@@ -0,0 +1,382 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import datetime
18
+ import logging
19
+ from typing import Any
20
+ from typing import Dict
21
+ from typing import List
22
+ from typing import Optional
23
+ import uuid
24
+
25
+ from a2a.server.events import Event as A2AEvent
26
+ from a2a.types import Artifact
27
+ from a2a.types import DataPart
28
+ from a2a.types import Message
29
+ from a2a.types import Role
30
+ from a2a.types import TaskArtifactUpdateEvent
31
+ from a2a.types import TaskState
32
+ from a2a.types import TaskStatus
33
+ from a2a.types import TaskStatusUpdateEvent
34
+ from a2a.types import TextPart
35
+
36
+ from ...agents.invocation_context import InvocationContext
37
+ from ...events.event import Event
38
+ from ...utils.feature_decorator import working_in_progress
39
+ from .part_converter import A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
40
+ from .part_converter import A2A_DATA_PART_METADATA_TYPE_KEY
41
+ from .part_converter import convert_genai_part_to_a2a_part
42
+ from .utils import _get_adk_metadata_key
43
+
44
+ # Constants
45
+
46
+ ARTIFACT_ID_SEPARATOR = "-"
47
+ DEFAULT_ERROR_MESSAGE = "An error occurred during processing"
48
+
49
+ # Logger
50
+ logger = logging.getLogger("google_adk." + __name__)
51
+
52
+
53
+ def _serialize_metadata_value(value: Any) -> str:
54
+ """Safely serializes metadata values to string format.
55
+
56
+ Args:
57
+ value: The value to serialize.
58
+
59
+ Returns:
60
+ String representation of the value.
61
+ """
62
+ if hasattr(value, "model_dump"):
63
+ try:
64
+ return value.model_dump(exclude_none=True, by_alias=True)
65
+ except Exception as e:
66
+ logger.warning("Failed to serialize metadata value: %s", e)
67
+ return str(value)
68
+ return str(value)
69
+
70
+
71
+ def _get_context_metadata(
72
+ event: Event, invocation_context: InvocationContext
73
+ ) -> Dict[str, str]:
74
+ """Gets the context metadata for the event.
75
+
76
+ Args:
77
+ event: The ADK event to extract metadata from.
78
+ invocation_context: The invocation context containing session information.
79
+
80
+ Returns:
81
+ A dictionary containing the context metadata.
82
+
83
+ Raises:
84
+ ValueError: If required fields are missing from event or context.
85
+ """
86
+ if not event:
87
+ raise ValueError("Event cannot be None")
88
+ if not invocation_context:
89
+ raise ValueError("Invocation context cannot be None")
90
+
91
+ try:
92
+ metadata = {
93
+ _get_adk_metadata_key("app_name"): invocation_context.app_name,
94
+ _get_adk_metadata_key("user_id"): invocation_context.user_id,
95
+ _get_adk_metadata_key("session_id"): invocation_context.session.id,
96
+ _get_adk_metadata_key("invocation_id"): event.invocation_id,
97
+ _get_adk_metadata_key("author"): event.author,
98
+ }
99
+
100
+ # Add optional metadata fields if present
101
+ optional_fields = [
102
+ ("branch", event.branch),
103
+ ("grounding_metadata", event.grounding_metadata),
104
+ ("custom_metadata", event.custom_metadata),
105
+ ("usage_metadata", event.usage_metadata),
106
+ ("error_code", event.error_code),
107
+ ]
108
+
109
+ for field_name, field_value in optional_fields:
110
+ if field_value is not None:
111
+ metadata[_get_adk_metadata_key(field_name)] = _serialize_metadata_value(
112
+ field_value
113
+ )
114
+
115
+ return metadata
116
+
117
+ except Exception as e:
118
+ logger.error("Failed to create context metadata: %s", e)
119
+ raise
120
+
121
+
122
+ def _create_artifact_id(
123
+ app_name: str, user_id: str, session_id: str, filename: str, version: int
124
+ ) -> str:
125
+ """Creates a unique artifact ID.
126
+
127
+ Args:
128
+ app_name: The application name.
129
+ user_id: The user ID.
130
+ session_id: The session ID.
131
+ filename: The artifact filename.
132
+ version: The artifact version.
133
+
134
+ Returns:
135
+ A unique artifact ID string.
136
+ """
137
+ components = [app_name, user_id, session_id, filename, str(version)]
138
+ return ARTIFACT_ID_SEPARATOR.join(components)
139
+
140
+
141
+ def _convert_artifact_to_a2a_events(
142
+ event: Event,
143
+ invocation_context: InvocationContext,
144
+ filename: str,
145
+ version: int,
146
+ ) -> TaskArtifactUpdateEvent:
147
+ """Converts a new artifact version to an A2A TaskArtifactUpdateEvent.
148
+
149
+ Args:
150
+ event: The ADK event containing the artifact information.
151
+ invocation_context: The invocation context.
152
+ filename: The name of the artifact file.
153
+ version: The version number of the artifact.
154
+
155
+ Returns:
156
+ A TaskArtifactUpdateEvent representing the artifact update.
157
+
158
+ Raises:
159
+ ValueError: If required parameters are invalid.
160
+ RuntimeError: If artifact loading fails.
161
+ """
162
+ if not filename:
163
+ raise ValueError("Filename cannot be empty")
164
+ if version < 0:
165
+ raise ValueError("Version must be non-negative")
166
+
167
+ try:
168
+ artifact_part = invocation_context.artifact_service.load_artifact(
169
+ app_name=invocation_context.app_name,
170
+ user_id=invocation_context.user_id,
171
+ session_id=invocation_context.session.id,
172
+ filename=filename,
173
+ version=version,
174
+ )
175
+
176
+ converted_part = convert_genai_part_to_a2a_part(part=artifact_part)
177
+ if not converted_part:
178
+ raise RuntimeError(f"Failed to convert artifact part for {filename}")
179
+
180
+ artifact_id = _create_artifact_id(
181
+ invocation_context.app_name,
182
+ invocation_context.user_id,
183
+ invocation_context.session.id,
184
+ filename,
185
+ version,
186
+ )
187
+
188
+ return TaskArtifactUpdateEvent(
189
+ taskId=str(uuid.uuid4()),
190
+ append=False,
191
+ contextId=invocation_context.session.id,
192
+ lastChunk=True,
193
+ artifact=Artifact(
194
+ artifactId=artifact_id,
195
+ name=filename,
196
+ metadata={
197
+ "filename": filename,
198
+ "version": version,
199
+ },
200
+ parts=[converted_part],
201
+ ),
202
+ )
203
+ except Exception as e:
204
+ logger.error(
205
+ "Failed to convert artifact for %s, version %s: %s",
206
+ filename,
207
+ version,
208
+ e,
209
+ )
210
+ raise RuntimeError(f"Artifact conversion failed: {e}") from e
211
+
212
+
213
+ def _process_long_running_tool(a2a_part, event: Event) -> None:
214
+ """Processes long-running tool metadata for an A2A part.
215
+
216
+ Args:
217
+ a2a_part: The A2A part to potentially mark as long-running.
218
+ event: The ADK event containing long-running tool information.
219
+ """
220
+ if (
221
+ isinstance(a2a_part.root, DataPart)
222
+ and event.long_running_tool_ids
223
+ and a2a_part.root.metadata.get(
224
+ _get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)
225
+ )
226
+ == A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
227
+ and a2a_part.root.metadata.get("id") in event.long_running_tool_ids
228
+ ):
229
+ a2a_part.root.metadata[_get_adk_metadata_key("is_long_running")] = True
230
+
231
+
232
+ @working_in_progress
233
+ def convert_event_to_a2a_status_message(
234
+ event: Event, invocation_context: InvocationContext
235
+ ) -> Optional[Message]:
236
+ """Converts an ADK event to an A2A message.
237
+
238
+ Args:
239
+ event: The ADK event to convert.
240
+ invocation_context: The invocation context.
241
+
242
+ Returns:
243
+ An A2A Message if the event has content, None otherwise.
244
+
245
+ Raises:
246
+ ValueError: If required parameters are invalid.
247
+ """
248
+ if not event:
249
+ raise ValueError("Event cannot be None")
250
+ if not invocation_context:
251
+ raise ValueError("Invocation context cannot be None")
252
+
253
+ if not event.content or not event.content.parts:
254
+ return None
255
+
256
+ try:
257
+ a2a_parts = []
258
+ for part in event.content.parts:
259
+ a2a_part = convert_genai_part_to_a2a_part(part)
260
+ if a2a_part:
261
+ a2a_parts.append(a2a_part)
262
+ _process_long_running_tool(a2a_part, event)
263
+
264
+ if a2a_parts:
265
+ return Message(
266
+ messageId=str(uuid.uuid4()), role=Role.agent, parts=a2a_parts
267
+ )
268
+
269
+ except Exception as e:
270
+ logger.error("Failed to convert event to status message: %s", e)
271
+ raise
272
+
273
+ return None
274
+
275
+
276
+ def _create_error_status_event(
277
+ event: Event, invocation_context: InvocationContext
278
+ ) -> TaskStatusUpdateEvent:
279
+ """Creates a TaskStatusUpdateEvent for error scenarios.
280
+
281
+ Args:
282
+ event: The ADK event containing error information.
283
+ invocation_context: The invocation context.
284
+
285
+ Returns:
286
+ A TaskStatusUpdateEvent with FAILED state.
287
+ """
288
+ error_message = getattr(event, "error_message", None) or DEFAULT_ERROR_MESSAGE
289
+
290
+ return TaskStatusUpdateEvent(
291
+ taskId=str(uuid.uuid4()),
292
+ contextId=invocation_context.session.id,
293
+ final=False,
294
+ metadata=_get_context_metadata(event, invocation_context),
295
+ status=TaskStatus(
296
+ state=TaskState.failed,
297
+ message=Message(
298
+ messageId=str(uuid.uuid4()),
299
+ role=Role.agent,
300
+ parts=[TextPart(text=error_message)],
301
+ ),
302
+ timestamp=datetime.datetime.now().isoformat(),
303
+ ),
304
+ )
305
+
306
+
307
+ def _create_running_status_event(
308
+ message: Message, invocation_context: InvocationContext, event: Event
309
+ ) -> TaskStatusUpdateEvent:
310
+ """Creates a TaskStatusUpdateEvent for running scenarios.
311
+
312
+ Args:
313
+ message: The A2A message to include.
314
+ invocation_context: The invocation context.
315
+ event: The ADK event.
316
+
317
+ Returns:
318
+ A TaskStatusUpdateEvent with RUNNING state.
319
+ """
320
+ return TaskStatusUpdateEvent(
321
+ taskId=str(uuid.uuid4()),
322
+ contextId=invocation_context.session.id,
323
+ final=False,
324
+ status=TaskStatus(
325
+ state=TaskState.working,
326
+ message=message,
327
+ timestamp=datetime.datetime.now().isoformat(),
328
+ ),
329
+ metadata=_get_context_metadata(event, invocation_context),
330
+ )
331
+
332
+
333
+ @working_in_progress
334
+ def convert_event_to_a2a_events(
335
+ event: Event, invocation_context: InvocationContext
336
+ ) -> List[A2AEvent]:
337
+ """Converts a GenAI event to a list of A2A events.
338
+
339
+ Args:
340
+ event: The ADK event to convert.
341
+ invocation_context: The invocation context.
342
+
343
+ Returns:
344
+ A list of A2A events representing the converted ADK event.
345
+
346
+ Raises:
347
+ ValueError: If required parameters are invalid.
348
+ """
349
+ if not event:
350
+ raise ValueError("Event cannot be None")
351
+ if not invocation_context:
352
+ raise ValueError("Invocation context cannot be None")
353
+
354
+ a2a_events = []
355
+
356
+ try:
357
+ # Handle artifact deltas
358
+ if event.actions.artifact_delta:
359
+ for filename, version in event.actions.artifact_delta.items():
360
+ artifact_event = _convert_artifact_to_a2a_events(
361
+ event, invocation_context, filename, version
362
+ )
363
+ a2a_events.append(artifact_event)
364
+
365
+ # Handle error scenarios
366
+ if event.error_code:
367
+ error_event = _create_error_status_event(event, invocation_context)
368
+ a2a_events.append(error_event)
369
+
370
+ # Handle regular message content
371
+ message = convert_event_to_a2a_status_message(event, invocation_context)
372
+ if message:
373
+ running_event = _create_running_status_event(
374
+ message, invocation_context, event
375
+ )
376
+ a2a_events.append(running_event)
377
+
378
+ except Exception as e:
379
+ logger.error("Failed to convert event to A2A events: %s", e)
380
+ raise
381
+
382
+ return a2a_events
@@ -20,9 +20,22 @@ from __future__ import annotations
20
20
 
21
21
  import json
22
22
  import logging
23
+ import sys
23
24
  from typing import Optional
24
25
 
25
- from a2a import types as a2a_types
26
+ from .utils import _get_adk_metadata_key
27
+
28
+ try:
29
+ from a2a import types as a2a_types
30
+ except ImportError as e:
31
+ if sys.version_info < (3, 10):
32
+ raise ImportError(
33
+ 'A2A Tool requires Python 3.10 or above. Please upgrade your Python'
34
+ ' version.'
35
+ ) from e
36
+ else:
37
+ raise e
38
+
26
39
  from google.genai import types as genai_types
27
40
 
28
41
  from ...utils.feature_decorator import working_in_progress
@@ -73,7 +86,7 @@ def convert_a2a_part_to_genai_part(
73
86
  # logic accordinlgy
74
87
  if part.metadata and A2A_DATA_PART_METADATA_TYPE_KEY in part.metadata:
75
88
  if (
76
- part.metadata[A2A_DATA_PART_METADATA_TYPE_KEY]
89
+ part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
77
90
  == A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
78
91
  ):
79
92
  return genai_types.Part(
@@ -82,7 +95,7 @@ def convert_a2a_part_to_genai_part(
82
95
  )
83
96
  )
84
97
  if (
85
- part.metadata[A2A_DATA_PART_METADATA_TYPE_KEY]
98
+ part.metadata[_get_adk_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY)]
86
99
  == A2A_DATA_PART_METADATA_TYPE_FUNCTION_RESPONSE
87
100
  ):
88
101
  return genai_types.Part(
@@ -0,0 +1,34 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ ADK_METADATA_KEY_PREFIX = "adk_"
18
+
19
+
20
+ def _get_adk_metadata_key(key: str) -> str:
21
+ """Gets the A2A event metadata key for the given key.
22
+
23
+ Args:
24
+ key: The metadata key to prefix.
25
+
26
+ Returns:
27
+ The prefixed metadata key.
28
+
29
+ Raises:
30
+ ValueError: If key is empty or None.
31
+ """
32
+ if not key:
33
+ raise ValueError("Metadata key cannot be empty or None")
34
+ return f"{ADK_METADATA_KEY_PREFIX}{key}"
@@ -230,4 +230,3 @@ class AuthCredential(BaseModelWithConfig):
230
230
  http: Optional[HttpAuth] = None
231
231
  service_account: Optional[ServiceAccount] = None
232
232
  oauth2: Optional[OAuth2Auth] = None
233
- google_oauth2_json: Optional[str] = None
@@ -76,11 +76,7 @@ class CredentialManager:
76
76
  self._refresher_registry = CredentialRefresherRegistry()
77
77
 
78
78
  # Register default exchangers and refreshers
79
- from .exchanger.service_account_credential_exchanger import ServiceAccountCredentialExchanger
80
-
81
- self._exchanger_registry.register(
82
- AuthCredentialTypes.SERVICE_ACCOUNT, ServiceAccountCredentialExchanger()
83
- )
79
+ # TODO: support service account credential exchanger
84
80
  from .refresher.oauth2_credential_refresher import OAuth2CredentialRefresher
85
81
 
86
82
  oauth2_refresher = OAuth2CredentialRefresher()
@@ -15,9 +15,7 @@
15
15
  """Credential exchanger module."""
16
16
 
17
17
  from .base_credential_exchanger import BaseCredentialExchanger
18
- from .service_account_credential_exchanger import ServiceAccountCredentialExchanger
19
18
 
20
19
  __all__ = [
21
20
  "BaseCredentialExchanger",
22
- "ServiceAccountCredentialExchanger",
23
21
  ]
@@ -60,27 +60,12 @@ class OAuth2CredentialRefresher(BaseCredentialRefresher):
60
60
  Returns:
61
61
  True if the credential needs to be refreshed, False otherwise.
62
62
  """
63
- # Handle Google OAuth2 credentials (from service account exchange)
64
- if auth_credential.google_oauth2_json:
65
- try:
66
- google_credential = Credentials.from_authorized_user_info(
67
- json.loads(auth_credential.google_oauth2_json)
68
- )
69
- return google_credential.expired and bool(
70
- google_credential.refresh_token
71
- )
72
- except Exception as e:
73
- logger.warning("Failed to parse Google OAuth2 JSON credential: %s", e)
74
- return False
75
63
 
76
64
  # Handle regular OAuth2 credentials
77
- elif auth_credential.oauth2 and auth_scheme:
65
+ if auth_credential.oauth2:
78
66
  if not AUTHLIB_AVIALABLE:
79
67
  return False
80
68
 
81
- if not auth_credential.oauth2:
82
- return False
83
-
84
69
  return OAuth2Token({
85
70
  "expires_at": auth_credential.oauth2.expires_at,
86
71
  "expires_in": auth_credential.oauth2.expires_in,
@@ -105,22 +90,9 @@ class OAuth2CredentialRefresher(BaseCredentialRefresher):
105
90
  The refreshed credential.
106
91
 
107
92
  """
108
- # Handle Google OAuth2 credentials (from service account exchange)
109
- if auth_credential.google_oauth2_json:
110
- try:
111
- google_credential = Credentials.from_authorized_user_info(
112
- json.loads(auth_credential.google_oauth2_json)
113
- )
114
- if google_credential.expired and google_credential.refresh_token:
115
- google_credential.refresh(Request())
116
- auth_credential.google_oauth2_json = google_credential.to_json()
117
- logger.info("Successfully refreshed Google OAuth2 JSON credential")
118
- except Exception as e:
119
- # TODO reconsider whether we should raise error when refresh failed.
120
- logger.error("Failed to refresh Google OAuth2 JSON credential: %s", e)
121
93
 
122
94
  # Handle regular OAuth2 credentials
123
- elif auth_credential.oauth2 and auth_scheme:
95
+ if auth_credential.oauth2 and auth_scheme:
124
96
  if not AUTHLIB_AVIALABLE:
125
97
  return auth_credential
126
98
 
@@ -288,8 +288,7 @@ async def handle_function_calls_live(
288
288
  trace_tool_call(
289
289
  tool=tool,
290
290
  args=function_args,
291
- response_event_id=function_response_event.id,
292
- function_response=function_response,
291
+ function_response_event=function_response_event,
293
292
  )
294
293
  function_response_events.append(function_response_event)
295
294
 
@@ -14,6 +14,7 @@
14
14
  from __future__ import annotations
15
15
 
16
16
  import asyncio
17
+ import json
17
18
  import logging
18
19
  import re
19
20
  from typing import Any
@@ -87,6 +88,7 @@ class VertexAiSessionService(BaseSessionService):
87
88
  path=f'reasoningEngines/{reasoning_engine_id}/sessions',
88
89
  request_dict=session_json_dict,
89
90
  )
91
+ api_response = _convert_api_response(api_response)
90
92
  logger.info(f'Create Session response {api_response}')
91
93
 
92
94
  session_id = api_response['name'].split('/')[-3]
@@ -100,6 +102,7 @@ class VertexAiSessionService(BaseSessionService):
100
102
  path=f'operations/{operation_id}',
101
103
  request_dict={},
102
104
  )
105
+ lro_response = _convert_api_response(lro_response)
103
106
 
104
107
  if lro_response.get('done', None):
105
108
  break
@@ -118,6 +121,7 @@ class VertexAiSessionService(BaseSessionService):
118
121
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session_id}',
119
122
  request_dict={},
120
123
  )
124
+ get_session_api_response = _convert_api_response(get_session_api_response)
121
125
 
122
126
  update_timestamp = isoparse(
123
127
  get_session_api_response['updateTime']
@@ -149,6 +153,7 @@ class VertexAiSessionService(BaseSessionService):
149
153
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session_id}',
150
154
  request_dict={},
151
155
  )
156
+ get_session_api_response = _convert_api_response(get_session_api_response)
152
157
 
153
158
  session_id = get_session_api_response['name'].split('/')[-1]
154
159
  update_timestamp = isoparse(
@@ -167,9 +172,12 @@ class VertexAiSessionService(BaseSessionService):
167
172
  path=f'reasoningEngines/{reasoning_engine_id}/sessions/{session_id}/events',
168
173
  request_dict={},
169
174
  )
175
+ list_events_api_response = _convert_api_response(list_events_api_response)
170
176
 
171
177
  # Handles empty response case
172
- if list_events_api_response.get('httpHeaders', None):
178
+ if not list_events_api_response or list_events_api_response.get(
179
+ 'httpHeaders', None
180
+ ):
173
181
  return session
174
182
 
175
183
  session.events += [
@@ -226,9 +234,10 @@ class VertexAiSessionService(BaseSessionService):
226
234
  path=path,
227
235
  request_dict={},
228
236
  )
237
+ api_response = _convert_api_response(api_response)
229
238
 
230
239
  # Handles empty response case
231
- if api_response.get('httpHeaders', None):
240
+ if not api_response or api_response.get('httpHeaders', None):
232
241
  return ListSessionsResponse()
233
242
 
234
243
  sessions = []
@@ -303,6 +312,13 @@ class VertexAiSessionService(BaseSessionService):
303
312
  return client._api_client
304
313
 
305
314
 
315
+ def _convert_api_response(api_response):
316
+ """Converts the API response to a JSON object based on the type."""
317
+ if hasattr(api_response, 'body'):
318
+ return json.loads(api_response.body)
319
+ return api_response
320
+
321
+
306
322
  def _convert_event_to_json(event: Event) -> Dict[str, Any]:
307
323
  metadata_json = {
308
324
  'partial': event.partial,
@@ -21,9 +21,10 @@ from typing import Optional
21
21
  from fastapi.openapi.models import OAuth2
22
22
  from fastapi.openapi.models import OAuthFlowAuthorizationCode
23
23
  from fastapi.openapi.models import OAuthFlows
24
+ import google.auth.credentials
24
25
  from google.auth.exceptions import RefreshError
25
26
  from google.auth.transport.requests import Request
26
- from google.oauth2.credentials import Credentials
27
+ import google.oauth2.credentials
27
28
  from pydantic import BaseModel
28
29
  from pydantic import model_validator
29
30
 
@@ -40,26 +41,35 @@ BIGQUERY_DEFAULT_SCOPE = ["https://www.googleapis.com/auth/bigquery"]
40
41
 
41
42
  @experimental
42
43
  class BigQueryCredentialsConfig(BaseModel):
43
- """Configuration for Google API tools. (Experimental)"""
44
+ """Configuration for Google API tools (Experimental).
45
+
46
+ Please do not use this in production, as it may be deprecated later.
47
+ """
44
48
 
45
49
  # Configure the model to allow arbitrary types like Credentials
46
50
  model_config = {"arbitrary_types_allowed": True}
47
51
 
48
- credentials: Optional[Credentials] = None
49
- """the existing oauth credentials to use. If set,this credential will be used
52
+ credentials: Optional[google.auth.credentials.Credentials] = None
53
+ """The existing auth credentials to use. If set, this credential will be used
50
54
  for every end user, end users don't need to be involved in the oauthflow. This
51
55
  field is mutually exclusive with client_id, client_secret and scopes.
52
56
  Don't set this field unless you are sure this credential has the permission to
53
57
  access every end user's data.
54
58
 
55
- Example usage: when the agent is deployed in Google Cloud environment and
59
+ Example usage 1: When the agent is deployed in Google Cloud environment and
56
60
  the service account (used as application default credentials) has access to
57
61
  all the required BigQuery resource. Setting this credential to allow user to
58
62
  access the BigQuery resource without end users going through oauth flow.
59
63
 
60
- To get application default credential: `google.auth.default(...)`. See more
64
+ To get application default credential, use: `google.auth.default(...)`. See more
61
65
  details in https://cloud.google.com/docs/authentication/application-default-credentials.
62
66
 
67
+ Example usage 2: When the agent wants to access the user's BigQuery resources
68
+ using the service account key credentials.
69
+
70
+ To load service account key credentials, use: `google.auth.load_credentials_from_file(...)`.
71
+ See more details in https://cloud.google.com/iam/docs/service-account-creds#user-managed-keys.
72
+
63
73
  When the deployed environment cannot provide a pre-existing credential,
64
74
  consider setting below client_id, client_secret and scope for end users to go
65
75
  through oauth flow, so that agent can access the user data.
@@ -86,7 +96,9 @@ class BigQueryCredentialsConfig(BaseModel):
86
96
  " client_id/client_secret/scopes."
87
97
  )
88
98
 
89
- if self.credentials:
99
+ if self.credentials and isinstance(
100
+ self.credentials, google.oauth2.credentials.Credentials
101
+ ):
90
102
  self.client_id = self.credentials.client_id
91
103
  self.client_secret = self.credentials.client_secret
92
104
  self.scopes = self.credentials.scopes
@@ -115,7 +127,7 @@ class BigQueryCredentialsManager:
115
127
 
116
128
  async def get_valid_credentials(
117
129
  self, tool_context: ToolContext
118
- ) -> Optional[Credentials]:
130
+ ) -> Optional[google.auth.credentials.Credentials]:
119
131
  """Get valid credentials, handling refresh and OAuth flow as needed.
120
132
 
121
133
  Args:
@@ -127,7 +139,7 @@ class BigQueryCredentialsManager:
127
139
  # First, try to get credentials from the tool context
128
140
  creds_json = tool_context.state.get(BIGQUERY_TOKEN_CACHE_KEY, None)
129
141
  creds = (
130
- Credentials.from_authorized_user_info(
142
+ google.oauth2.credentials.Credentials.from_authorized_user_info(
131
143
  json.loads(creds_json), self.credentials_config.scopes
132
144
  )
133
145
  if creds_json
@@ -138,6 +150,11 @@ class BigQueryCredentialsManager:
138
150
  if not creds:
139
151
  creds = self.credentials_config.credentials
140
152
 
153
+ # If non-oauth credentials are provided then use them as is. This helps
154
+ # in flows such as service account keys
155
+ if creds and not isinstance(creds, google.oauth2.credentials.Credentials):
156
+ return creds
157
+
141
158
  # Check if we have valid credentials
142
159
  if creds and creds.valid:
143
160
  return creds
@@ -159,7 +176,7 @@ class BigQueryCredentialsManager:
159
176
 
160
177
  async def _perform_oauth_flow(
161
178
  self, tool_context: ToolContext
162
- ) -> Optional[Credentials]:
179
+ ) -> Optional[google.oauth2.credentials.Credentials]:
163
180
  """Perform OAuth flow to get new credentials.
164
181
 
165
182
  Args:
@@ -199,7 +216,7 @@ class BigQueryCredentialsManager:
199
216
 
200
217
  if auth_response:
201
218
  # OAuth flow completed, create credentials
202
- creds = Credentials(
219
+ creds = google.oauth2.credentials.Credentials(
203
220
  token=auth_response.oauth2.access_token,
204
221
  refresh_token=auth_response.oauth2.refresh_token,
205
222
  token_uri=auth_scheme.flows.authorizationCode.tokenUrl,
@@ -19,7 +19,7 @@ from typing import Any
19
19
  from typing import Callable
20
20
  from typing import Optional
21
21
 
22
- from google.oauth2.credentials import Credentials
22
+ from google.auth.credentials import Credentials
23
23
  from typing_extensions import override
24
24
 
25
25
  from ...utils.feature_decorator import experimental
@@ -15,8 +15,8 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import google.api_core.client_info
18
+ from google.auth.credentials import Credentials
18
19
  from google.cloud import bigquery
19
- from google.oauth2.credentials import Credentials
20
20
 
21
21
  from ... import version
22
22
 
@@ -12,8 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from google.auth.credentials import Credentials
15
16
  from google.cloud import bigquery
16
- from google.oauth2.credentials import Credentials
17
17
 
18
18
  from . import client
19
19
 
@@ -16,8 +16,8 @@ import functools
16
16
  import types
17
17
  from typing import Callable
18
18
 
19
+ from google.auth.credentials import Credentials
19
20
  from google.cloud import bigquery
20
- from google.oauth2.credentials import Credentials
21
21
 
22
22
  from . import client
23
23
  from .config import BigQueryToolConfig
@@ -138,11 +138,6 @@ class MCPTool(BaseAuthenticatedTool):
138
138
  if credential:
139
139
  if credential.oauth2:
140
140
  headers = {"Authorization": f"Bearer {credential.oauth2.access_token}"}
141
- elif credential.google_oauth2_json:
142
- google_credential = Credentials.from_authorized_user_info(
143
- json.loads(credential.google_oauth2_json)
144
- )
145
- headers = {"Authorization": f"Bearer {google_credential.token}"}
146
141
  elif credential.http:
147
142
  # Handle HTTP authentication schemes
148
143
  if (
@@ -178,10 +173,9 @@ class MCPTool(BaseAuthenticatedTool):
178
173
  headers = {"X-API-Key": credential.api_key}
179
174
  elif credential.service_account:
180
175
  # Service accounts should be exchanged for access tokens before reaching this point
181
- # If we reach here, we can try to use google_oauth2_json or log a warning
182
176
  logger.warning(
183
- "Service account credentials should be exchanged for access"
184
- " tokens before MCP session creation"
177
+ "Service account credentials should be exchanged before MCP"
178
+ " session creation"
185
179
  )
186
180
 
187
181
  return headers
@@ -233,7 +233,6 @@ class ToolAuthHandler:
233
233
  AuthCredentialTypes.OPEN_ID_CONNECT,
234
234
  )
235
235
  and not credential.oauth2.access_token
236
- and not credential.google_oauth2_json
237
236
  )
238
237
 
239
238
  async def prepare_auth_credentials(
google/adk/version.py CHANGED
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
 
15
15
  # version: major.minor.patch
16
- __version__ = "1.4.0"
16
+ __version__ = "1.4.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-adk
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Agent Development Kit
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  Requires-Python: >=3.9
@@ -2,10 +2,12 @@ google/adk/__init__.py,sha256=sSPQK3r0tW8ahl-k8SXkZvMcbiTbGICCtrw6KkFucyg,726
2
2
  google/adk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  google/adk/runners.py,sha256=Tqv3HrulZGBO5YZ9bNbtAihDl-SsPv1SnW5nzUBRfZs,18558
4
4
  google/adk/telemetry.py,sha256=0ZHioyg4GD-A4xd2TPB_W1uW2_m5kMQGHosPwCu9cIc,8641
5
- google/adk/version.py,sha256=1bJw7-dWWcu5lzJG0xHN5h9smUy3_7Hc4dZY0XZLkug,626
5
+ google/adk/version.py,sha256=oy2jnAjqK4WVwWkp2NI0T0DqExGIXma2q1zr_CLnGO8,626
6
6
  google/adk/a2a/__init__.py,sha256=Q9FlRO2IfSE9yEaiAYzWkOMBJPCaNYqh4ihcp0t0BQs,574
7
7
  google/adk/a2a/converters/__init__.py,sha256=Q9FlRO2IfSE9yEaiAYzWkOMBJPCaNYqh4ihcp0t0BQs,574
8
- google/adk/a2a/converters/part_converter.py,sha256=2SxRkKRlhOih7mg33uefNVlBhOYOcOMomQhLSuV3_Kc,5056
8
+ google/adk/a2a/converters/event_converter.py,sha256=8n6_j7mkxCZlDMPhEfzG6THCCA0qSqV3PN0s3C-AT60,11183
9
+ google/adk/a2a/converters/part_converter.py,sha256=JAkL6x7zPhKvLNDSSacpdQl2v0YwzVZS804--q1yi3U,5374
10
+ google/adk/a2a/converters/utils.py,sha256=pMhM3k-KZuSprmB-ELH-sdRKLC5SZnRYJke6lCJJdJI,1011
9
11
  google/adk/agents/__init__.py,sha256=WsCiBlvI-ISWrcntboo_sULvVJNwLNxXCe42UGPLKdY,1041
10
12
  google/adk/agents/active_streaming_tool.py,sha256=vFuh_PkdF5EyyneBBJ7Al8ojeTIR3OtsxLjckr9DbXE,1194
11
13
  google/adk/agents/base_agent.py,sha256=fCwAcR12IVVx8qXWGVf773zOmQEKnXDPwmoYYQwUfR4,12168
@@ -25,25 +27,24 @@ google/adk/artifacts/base_artifact_service.py,sha256=H-t5nckLTfr330utj8vxjH45z81
25
27
  google/adk/artifacts/gcs_artifact_service.py,sha256=-YU4NhZiGMnHHCg00aJWgKq4JWkQLh7EH5OuGusM5bE,5608
26
28
  google/adk/artifacts/in_memory_artifact_service.py,sha256=Iw34Ja89JwGgd3sulbxxk5pVMqzEZJCt4F2m15MC37U,4059
27
29
  google/adk/auth/__init__.py,sha256=GoFe0aZGdp0ExNE4rXNn1RuXLaB64j7Z-2C5e2Hsh8c,908
28
- google/adk/auth/auth_credential.py,sha256=w_icK-fWNyhk_tr-mhX97tzqjuxmWIDnurmTQgRvLEo,7055
30
+ google/adk/auth/auth_credential.py,sha256=F1cMHe_gda5N6RXlyB0IYLLos-Jz-WcNFkIm9SjSiGQ,7012
29
31
  google/adk/auth/auth_handler.py,sha256=aUJrY8fzPvSZwi1rtB_N7fHuHt6DE-H8D8HnkDUCyZQ,6828
30
32
  google/adk/auth/auth_preprocessor.py,sha256=RleOG5I7L1EWVRdX_bC1WtKnt0FDKAcXSSh1RexJqtE,4309
31
33
  google/adk/auth/auth_schemes.py,sha256=dxx9bxjOWoae1fSVxbpaVTwa0I4v76_QJJFEX--1ueA,2260
32
34
  google/adk/auth/auth_tool.py,sha256=2N-lR5UrgBhwPaErwRusT1MrNDQGb8fuiMHO5x3cmck,3674
33
- google/adk/auth/credential_manager.py,sha256=OSJxYMJqTotyK4UueLeDDdmUfGuMuPC_nmwHjifPC_s,9671
35
+ google/adk/auth/credential_manager.py,sha256=G5iqpStGHl4wZxkeqNDNljrEt1L_XNSfNVM9mmOqUhg,9503
34
36
  google/adk/auth/oauth2_credential_util.py,sha256=gIb1OUGA4NZRnIeLLk29T_Q9J8ibTxe7-357UhCW3FY,3340
35
37
  google/adk/auth/credential_service/__init__.py,sha256=Q9FlRO2IfSE9yEaiAYzWkOMBJPCaNYqh4ihcp0t0BQs,574
36
38
  google/adk/auth/credential_service/base_credential_service.py,sha256=4SK1UW9NiLNLrc_grpG3xyIPXySGpl0DIv9bTm-onWE,2325
37
39
  google/adk/auth/credential_service/in_memory_credential_service.py,sha256=xXG-3_uq3CU1Eilp8NGBhje0X77ROlAT5BKyJmLLyHg,2161
38
- google/adk/auth/exchanger/__init__.py,sha256=HCZ8KUqeXORwhjXqHCuezCrXsyVYliuo8YrLXvxS9wY,845
40
+ google/adk/auth/exchanger/__init__.py,sha256=RHCK_Zg7hXzBKvz2Vrwvbx_cMXWittidIZToszaL3Nc,720
39
41
  google/adk/auth/exchanger/base_credential_exchanger.py,sha256=Uqzs_NhEDmuH5n0U_ES5fHlMSagyYEc5JKu-5GdOC_A,1644
40
42
  google/adk/auth/exchanger/credential_exchanger_registry.py,sha256=Nsk9BMmhFbZsXQwPckm8elXfbk6iIRSrvegR6DpcONo,1855
41
43
  google/adk/auth/exchanger/oauth2_credential_exchanger.py,sha256=FA0EYC-zfD1gO9CV9xDYYfPxU2EAJdYwVyWjVy3C6Bw,3649
42
- google/adk/auth/exchanger/service_account_credential_exchanger.py,sha256=AYVZcvOuOhNKPgwwgSOcxyZegi6xq4IW8vvNs_IMvXE,3804
43
44
  google/adk/auth/refresher/__init__.py,sha256=DEEkESlvEteCpO4QcDExm6K8S8y7l_oS-A2TK1Oh1xU,720
44
45
  google/adk/auth/refresher/base_credential_refresher.py,sha256=oDWBAuSnt5-00f8LwTrwTptuwkkUXknad8Zbp2mmOA4,2192
45
46
  google/adk/auth/refresher/credential_refresher_registry.py,sha256=ioHclgxCtpSeiUcMq9jwzPn0IzzxrQLpWSpHy90folA,1877
46
- google/adk/auth/refresher/oauth2_credential_refresher.py,sha256=TqpUMOo_Ln1bZSFYaTiHonSlUvb5-3Mn9npnYC2dF6g,5329
47
+ google/adk/auth/refresher/oauth2_credential_refresher.py,sha256=HARpIwwXNxqo4_CNDpRFrN-p7RsnShgu21hgRFVwv4Q,4035
47
48
  google/adk/cli/__init__.py,sha256=ouPYnIY02VmGNfpA6IT8oSQdfeZd1LHVoDSt_x8zQPU,609
48
49
  google/adk/cli/__main__.py,sha256=gN8rRWlkh_3gLI-oYByxrKpCW9BIfDwrr0YuyisxmHo,646
49
50
  google/adk/cli/agent_graph.py,sha256=Kj5_a4UE1QXmqdRv4i4LI4hKHOrLkBS22Q759F3aRug,9879
@@ -116,7 +117,7 @@ google/adk/flows/llm_flows/auto_flow.py,sha256=CnuFelyZhB_ns4U_5_dW0x_KQlzu02My7
116
117
  google/adk/flows/llm_flows/base_llm_flow.py,sha256=uGqJ-b_Xygqgae1dndNh9tgTaj3luMKq3Qj4_jDf278,22660
117
118
  google/adk/flows/llm_flows/basic.py,sha256=263pfk6PIqKO-7BWo--bsBbpZJ8u5R9OK__WhpBgIbM,2848
118
119
  google/adk/flows/llm_flows/contents.py,sha256=bAklBI8YWctd0pGQCRwCVDqDxASiCNV_t8tJChPLbFg,13055
119
- google/adk/flows/llm_flows/functions.py,sha256=r082klTW5MRHs8H3e5DgkeYwBDk8kEw583UsnqINiZk,17556
120
+ google/adk/flows/llm_flows/functions.py,sha256=NRzs9MfqCI8JjKBJCPk3iO_xXn0PzaBfy7NjQrdVsUU,17512
120
121
  google/adk/flows/llm_flows/identity.py,sha256=X4CRg12NvnopmydU9gbFJI4lW1_otN-w_GOAuPvKrXo,1651
121
122
  google/adk/flows/llm_flows/instructions.py,sha256=sO2dQ5hn6ybjXs2fWYWvEFVtACdpiiP0yKf9eNVjhhM,2879
122
123
  google/adk/flows/llm_flows/single_flow.py,sha256=gC677SxxammKx1XkZBzUdgBjDzeymKRcRQQxFGIur8Y,1904
@@ -151,7 +152,7 @@ google/adk/sessions/database_session_service.py,sha256=zPqH1pY_euvSA9I3f3BTfvBxL
151
152
  google/adk/sessions/in_memory_session_service.py,sha256=5mU882L-0zobyWoldAkuBMmGKTQV_XlhL0A2_OySPzU,9080
152
153
  google/adk/sessions/session.py,sha256=fwJ3D4rUQ1N5cLMpFrE_BstEz6Ct637FlF52MfkxZCk,1861
153
154
  google/adk/sessions/state.py,sha256=con9G5nfJpa95J5LKTAnZ3KMPkXdaTbrdwRdKg6d6B4,2299
154
- google/adk/sessions/vertex_ai_session_service.py,sha256=BvYXkP4zWdJ9WA65TWpmbzOMzGs5jwMprSm7DLgVmYE,12870
155
+ google/adk/sessions/vertex_ai_session_service.py,sha256=ZzQeCd3lE-7htfNZjWB_pDV2C5fNksHQ7c-RvJjSEnc,13564
155
156
  google/adk/tools/__init__.py,sha256=_5JFmTAPBmXlOY8CRaYJlZubvwxoxhi2esXusovv5ME,1752
156
157
  google/adk/tools/_automatic_function_calling_util.py,sha256=p5An_BklKDo1w5_DigZGgf0p023lIDKPaZPS_-iJN6k,11100
157
158
  google/adk/tools/_forwarding_artifact_service.py,sha256=MHOfc8ntSuHLcA4jp218FP0k0qWAu3-6MSQCNWZ__S4,3022
@@ -192,13 +193,13 @@ google/adk/tools/application_integration_tool/integration_connector_tool.py,sha2
192
193
  google/adk/tools/application_integration_tool/clients/connections_client.py,sha256=zspLmrx2DvOg2r5B2DWxeK3fKdQovIu8t3z5Xip7AGo,31428
193
194
  google/adk/tools/application_integration_tool/clients/integration_client.py,sha256=hLM8n97hsmvgYBtmF_KMYwr_mnlhfPvsDXzE2cI5SqE,10654
194
195
  google/adk/tools/bigquery/__init__.py,sha256=tkXX7IoTzXgZjv82fjqa0_TTufxijiIr6nPsaqH1o5Y,1446
195
- google/adk/tools/bigquery/bigquery_credentials.py,sha256=cfySW5x25M3hDlUdLN0j71PfCrQOZFfB35pOsQYxxRM,7844
196
- google/adk/tools/bigquery/bigquery_tool.py,sha256=uZEj_CGNsFrt23RbCF-9Hroru6mghxgzc_FvAgT11o8,4210
196
+ google/adk/tools/bigquery/bigquery_credentials.py,sha256=cs610IowXAhpwovRpMjspPQ6Bb9TB23IBqdUorHNQzI,8690
197
+ google/adk/tools/bigquery/bigquery_tool.py,sha256=Hvw5ijtX9FGJfUxvBCTL51JxAS4-G5wFtewMUcSACxk,4208
197
198
  google/adk/tools/bigquery/bigquery_toolset.py,sha256=4eIj_bbUeC4bhhNN9RA8i5661Xi3qM9pqfYoN4XBQME,2831
198
- google/adk/tools/bigquery/client.py,sha256=5kL2zVGj3bgo8X-G1ha7zdK6UrWc0sY_qq96cODELD8,1168
199
+ google/adk/tools/bigquery/client.py,sha256=CUFsxf6Ma5dTfofk1L888EnUfo2zlI6-_NFs-v3Hcr0,1166
199
200
  google/adk/tools/bigquery/config.py,sha256=GNiPUX4sVKg7UfoJ-xo8RCReYym4qF3blMUzY9O5CRE,1390
200
- google/adk/tools/bigquery/metadata_tool.py,sha256=1WOcI60oSTpwsuzB1s-__hNKZE0x0csuMdV7w6V0jYI,8265
201
- google/adk/tools/bigquery/query_tool.py,sha256=oTAb1_bFbzkG46dEYsnQfgyQxwaHotR1OSjvIqC62XM,6158
201
+ google/adk/tools/bigquery/metadata_tool.py,sha256=BZ5VPXERZgVJDp1VqT6vSmFkDYPbo0bXBZsE5RgL3XY,8263
202
+ google/adk/tools/bigquery/query_tool.py,sha256=-dsqZbuQxwYb6uCHJ5_-6Yt94QlvQ4ktDov6JKdsnvc,6156
202
203
  google/adk/tools/google_api_tool/__init__.py,sha256=a_Bco5SyTQ89yb1t6Bs6NQrTsJgV22mn1quRNygVZXw,1385
203
204
  google/adk/tools/google_api_tool/google_api_tool.py,sha256=vDA7YnDZCl8-ucBvIzXS-uJWX1m2KY_csTz5M3nw_Ys,2029
204
205
  google/adk/tools/google_api_tool/google_api_toolset.py,sha256=M27lSCXOka4yHGGH50UVJvoEvZbWT-QBVaR1tW018tY,3773
@@ -207,7 +208,7 @@ google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py,sha256=mo1ew3
207
208
  google/adk/tools/mcp_tool/__init__.py,sha256=UIXmz81_7s-kpZNSTOhpWXtQeMxXpjTwMRoDPDbCTkQ,1515
208
209
  google/adk/tools/mcp_tool/conversion_utils.py,sha256=PfPSBAPzAgBsEWk2goKOFHz4fM-9ralW-nMkCbs0b38,5339
209
210
  google/adk/tools/mcp_tool/mcp_session_manager.py,sha256=9QRcprgzfpUHBTePXE2rrUb3LKRQeGo38-Gb6LPbH2w,12946
210
- google/adk/tools/mcp_tool/mcp_tool.py,sha256=rqPTmd4xf5-oQjjLtdqwtAmjaYemZ6RlSdh3Y5iOR2A,6427
211
+ google/adk/tools/mcp_tool/mcp_tool.py,sha256=9tHuqpjBKqcMeI8WvVXfWuWvxov8kzuG85REboblpL8,6081
211
212
  google/adk/tools/mcp_tool/mcp_toolset.py,sha256=dp3IhfdrK07CvZ5LERVtUBzlbbUOhnrmI0ND8Fn2eGk,6323
212
213
  google/adk/tools/openapi_tool/__init__.py,sha256=UMsewNCQjd-r1GBX1OMuUJTzJ0AlQuegIc98g04-0oU,724
213
214
  google/adk/tools/openapi_tool/auth/__init__.py,sha256=NVRXscqN4V0CSCvIp8J_ee8Xyw4m-OGoZn7SmrtOsQk,637
@@ -224,7 +225,7 @@ google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py,sha256=
224
225
  google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py,sha256=zxQtizsdW8FIqhpSzGfhkQLbBXIDZOK_0h8TrXayYZ4,5532
225
226
  google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py,sha256=PhgkKRtSQi-gZa2RBeEzCX0A0Aekk2kLIo_cuf9aAQ0,9078
226
227
  google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py,sha256=TMLDr-ODSEiok0l1ObPmuwcTOvOAjuxoMNBw_QwG5Pg,14650
227
- google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py,sha256=ALVwmWZzvc9kvqp3lBovZ_N4_LY0F1CrPUItNn-PXFI,10059
228
+ google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py,sha256=9bqnlvmcr6i8Dab73ntzbynkFFWCYMhk7R-DS-jkOz4,10013
228
229
  google/adk/tools/retrieval/__init__.py,sha256=0euJjx0ReH8JmUI5-JU8kWRswqLxobRCDjx5zvX4rHY,1188
229
230
  google/adk/tools/retrieval/base_retrieval_tool.py,sha256=4aar8Kg-6rQG7Ht1n18D5fvJnuffodFdSjeCp-GzA7w,1174
230
231
  google/adk/tools/retrieval/files_retrieval.py,sha256=UvxXjs3t8O2VO7o4wagHah2ydHT6sl0bLMsKxDVTOHU,1271
@@ -234,8 +235,8 @@ google/adk/utils/__init__.py,sha256=Q9FlRO2IfSE9yEaiAYzWkOMBJPCaNYqh4ihcp0t0BQs,
234
235
  google/adk/utils/feature_decorator.py,sha256=DzGHMTStf4-S9BNiA4EqcCJbrdKijhgeSUSPdzM44H8,5048
235
236
  google/adk/utils/instructions_utils.py,sha256=al9Z-P8qOrbxNju8cqkeH7qRg0oQH7hfmvTG-0oSAQk,3996
236
237
  google/adk/utils/variant_utils.py,sha256=u9IuOn2aXG3ibDYshgLoogBXqH9Gd84ixArQoeLQiE8,1463
237
- google_adk-1.4.0.dist-info/entry_points.txt,sha256=zL9CU-6V2yQ2oc5lrcyj55ROHrpiIePsvQJ4H6SL-zI,43
238
- google_adk-1.4.0.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
239
- google_adk-1.4.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
240
- google_adk-1.4.0.dist-info/METADATA,sha256=ZJf1XYtlb1017k3bc5mNCXcOQuIwkevN2Lvx1avtj20,9981
241
- google_adk-1.4.0.dist-info/RECORD,,
238
+ google_adk-1.4.2.dist-info/entry_points.txt,sha256=zL9CU-6V2yQ2oc5lrcyj55ROHrpiIePsvQJ4H6SL-zI,43
239
+ google_adk-1.4.2.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
240
+ google_adk-1.4.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
241
+ google_adk-1.4.2.dist-info/METADATA,sha256=GFu7dUG7xYrhvSed3cY-WARhxOCrTlFmUMyzygCL2Ko,9981
242
+ google_adk-1.4.2.dist-info/RECORD,,
@@ -1,104 +0,0 @@
1
- # Copyright 2025 Google LLC
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- """Credential fetcher for Google Service Account."""
16
-
17
- from __future__ import annotations
18
-
19
- from typing import Optional
20
-
21
- import google.auth
22
- from google.auth.transport.requests import Request
23
- from google.oauth2 import service_account
24
- from typing_extensions import override
25
-
26
- from ...utils.feature_decorator import experimental
27
- from ..auth_credential import AuthCredential
28
- from ..auth_credential import AuthCredentialTypes
29
- from ..auth_schemes import AuthScheme
30
- from .base_credential_exchanger import BaseCredentialExchanger
31
-
32
-
33
- @experimental
34
- class ServiceAccountCredentialExchanger(BaseCredentialExchanger):
35
- """Exchanges Google Service Account credentials for an access token.
36
-
37
- Uses the default service credential if `use_default_credential = True`.
38
- Otherwise, uses the service account credential provided in the auth
39
- credential.
40
- """
41
-
42
- @override
43
- async def exchange(
44
- self,
45
- auth_credential: AuthCredential,
46
- auth_scheme: Optional[AuthScheme] = None,
47
- ) -> AuthCredential:
48
- """Exchanges the service account auth credential for an access token.
49
-
50
- If the AuthCredential contains a service account credential, it will be used
51
- to exchange for an access token. Otherwise, if use_default_credential is True,
52
- the default application credential will be used for exchanging an access token.
53
-
54
- Args:
55
- auth_scheme: The authentication scheme.
56
- auth_credential: The credential to exchange.
57
-
58
- Returns:
59
- An AuthCredential in OAUTH2 format, containing the exchanged credential JSON.
60
-
61
- Raises:
62
- ValueError: If service account credentials are missing or invalid.
63
- Exception: If credential exchange or refresh fails.
64
- """
65
- if auth_credential is None:
66
- raise ValueError("Credential cannot be None.")
67
-
68
- if auth_credential.auth_type != AuthCredentialTypes.SERVICE_ACCOUNT:
69
- raise ValueError("Credential is not a service account credential.")
70
-
71
- if auth_credential.service_account is None:
72
- raise ValueError(
73
- "Service account credentials are missing. Please provide them."
74
- )
75
-
76
- if (
77
- auth_credential.service_account.service_account_credential is None
78
- and not auth_credential.service_account.use_default_credential
79
- ):
80
- raise ValueError(
81
- "Service account credentials are invalid. Please set the"
82
- " service_account_credential field or set `use_default_credential ="
83
- " True` to use application default credential in a hosted service"
84
- " like Google Cloud Run."
85
- )
86
-
87
- try:
88
- if auth_credential.service_account.use_default_credential:
89
- credentials, _ = google.auth.default()
90
- else:
91
- config = auth_credential.service_account
92
- credentials = service_account.Credentials.from_service_account_info(
93
- config.service_account_credential.model_dump(), scopes=config.scopes
94
- )
95
-
96
- # Refresh credentials to ensure we have a valid access token
97
- credentials.refresh(Request())
98
-
99
- return AuthCredential(
100
- auth_type=AuthCredentialTypes.OAUTH2,
101
- google_oauth2_json=credentials.to_json(),
102
- )
103
- except Exception as e:
104
- raise ValueError(f"Failed to exchange service account token: {e}") from e