google-adk 1.6.1__py3-none-any.whl → 1.7.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 (81) hide show
  1. google/adk/a2a/converters/event_converter.py +5 -85
  2. google/adk/a2a/executor/a2a_agent_executor.py +45 -16
  3. google/adk/agents/__init__.py +5 -0
  4. google/adk/agents/agent_config.py +46 -0
  5. google/adk/agents/base_agent.py +234 -41
  6. google/adk/agents/callback_context.py +41 -0
  7. google/adk/agents/common_configs.py +79 -0
  8. google/adk/agents/config_agent_utils.py +184 -0
  9. google/adk/agents/config_schemas/AgentConfig.json +544 -0
  10. google/adk/agents/invocation_context.py +5 -1
  11. google/adk/agents/llm_agent.py +190 -9
  12. google/adk/agents/loop_agent.py +29 -0
  13. google/adk/agents/parallel_agent.py +24 -3
  14. google/adk/agents/remote_a2a_agent.py +15 -3
  15. google/adk/agents/sequential_agent.py +22 -1
  16. google/adk/artifacts/gcs_artifact_service.py +24 -2
  17. google/adk/auth/auth_handler.py +3 -3
  18. google/adk/auth/credential_manager.py +23 -23
  19. google/adk/auth/credential_service/base_credential_service.py +6 -6
  20. google/adk/auth/credential_service/in_memory_credential_service.py +10 -8
  21. google/adk/auth/credential_service/session_state_credential_service.py +8 -8
  22. google/adk/auth/exchanger/oauth2_credential_exchanger.py +3 -3
  23. google/adk/auth/oauth2_credential_util.py +2 -2
  24. google/adk/auth/refresher/oauth2_credential_refresher.py +4 -4
  25. google/adk/cli/agent_graph.py +3 -1
  26. google/adk/cli/browser/index.html +1 -1
  27. google/adk/cli/browser/main-SRBSE46V.js +3914 -0
  28. google/adk/cli/browser/polyfills-B6TNHZQ6.js +17 -0
  29. google/adk/cli/fast_api.py +42 -2
  30. google/adk/cli/utils/agent_loader.py +35 -1
  31. google/adk/code_executors/base_code_executor.py +14 -19
  32. google/adk/code_executors/built_in_code_executor.py +4 -1
  33. google/adk/evaluation/base_eval_service.py +46 -2
  34. google/adk/evaluation/evaluation_generator.py +1 -1
  35. google/adk/evaluation/in_memory_eval_sets_manager.py +151 -0
  36. google/adk/evaluation/local_eval_service.py +389 -0
  37. google/adk/evaluation/local_eval_sets_manager.py +23 -8
  38. google/adk/flows/llm_flows/auto_flow.py +6 -11
  39. google/adk/flows/llm_flows/base_llm_flow.py +41 -23
  40. google/adk/flows/llm_flows/contents.py +16 -10
  41. google/adk/flows/llm_flows/functions.py +76 -33
  42. google/adk/memory/in_memory_memory_service.py +20 -14
  43. google/adk/models/anthropic_llm.py +44 -5
  44. google/adk/models/google_llm.py +11 -6
  45. google/adk/models/lite_llm.py +21 -4
  46. google/adk/plugins/__init__.py +17 -0
  47. google/adk/plugins/base_plugin.py +317 -0
  48. google/adk/plugins/plugin_manager.py +265 -0
  49. google/adk/runners.py +122 -18
  50. google/adk/sessions/database_session_service.py +26 -28
  51. google/adk/sessions/vertex_ai_session_service.py +14 -7
  52. google/adk/tools/agent_tool.py +1 -0
  53. google/adk/tools/apihub_tool/apihub_toolset.py +38 -39
  54. google/adk/tools/application_integration_tool/application_integration_toolset.py +35 -37
  55. google/adk/tools/application_integration_tool/integration_connector_tool.py +2 -3
  56. google/adk/tools/base_tool.py +9 -9
  57. google/adk/tools/base_toolset.py +7 -5
  58. google/adk/tools/bigquery/__init__.py +3 -3
  59. google/adk/tools/enterprise_search_tool.py +4 -2
  60. google/adk/tools/google_api_tool/google_api_tool.py +16 -1
  61. google/adk/tools/google_api_tool/google_api_toolset.py +9 -7
  62. google/adk/tools/google_api_tool/google_api_toolsets.py +41 -20
  63. google/adk/tools/google_search_tool.py +4 -2
  64. google/adk/tools/langchain_tool.py +2 -3
  65. google/adk/tools/long_running_tool.py +21 -0
  66. google/adk/tools/mcp_tool/mcp_toolset.py +27 -28
  67. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +8 -8
  68. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +4 -6
  69. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +3 -2
  70. google/adk/tools/tool_context.py +0 -10
  71. google/adk/tools/url_context_tool.py +4 -2
  72. google/adk/tools/vertex_ai_search_tool.py +4 -2
  73. google/adk/utils/model_name_utils.py +90 -0
  74. google/adk/version.py +1 -1
  75. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/METADATA +2 -2
  76. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/RECORD +79 -69
  77. google/adk/cli/browser/main-RXDVX3K6.js +0 -3914
  78. google/adk/cli/browser/polyfills-FFHMD2TL.js +0 -17
  79. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/WHEEL +0 -0
  80. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/entry_points.txt +0 -0
  81. {google_adk-1.6.1.dist-info → google_adk-1.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,14 +14,17 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import importlib
17
18
  import inspect
18
19
  import logging
19
20
  from typing import Any
20
21
  from typing import AsyncGenerator
21
22
  from typing import Awaitable
22
23
  from typing import Callable
24
+ from typing import List
23
25
  from typing import Literal
24
26
  from typing import Optional
27
+ from typing import Type
25
28
  from typing import Union
26
29
 
27
30
  from google.genai import types
@@ -48,8 +51,11 @@ from ..tools.base_tool import BaseTool
48
51
  from ..tools.base_toolset import BaseToolset
49
52
  from ..tools.function_tool import FunctionTool
50
53
  from ..tools.tool_context import ToolContext
54
+ from ..utils.feature_decorator import working_in_progress
51
55
  from .base_agent import BaseAgent
56
+ from .base_agent import BaseAgentConfig
52
57
  from .callback_context import CallbackContext
58
+ from .common_configs import CodeConfig
53
59
  from .invocation_context import InvocationContext
54
60
  from .readonly_context import ReadonlyContext
55
61
 
@@ -164,9 +170,9 @@ class LlmAgent(BaseAgent):
164
170
  """Controls content inclusion in model requests.
165
171
 
166
172
  Options:
167
- default: Model receives relevant conversation history
168
- none: Model receives no prior history, operates solely on current
169
- instruction and input
173
+ default: Model receives relevant conversation history
174
+ none: Model receives no prior history, operates solely on current
175
+ instruction and input
170
176
  """
171
177
 
172
178
  # Controlled input/output configurations - Start
@@ -175,8 +181,9 @@ class LlmAgent(BaseAgent):
175
181
  output_schema: Optional[type[BaseModel]] = None
176
182
  """The output schema when agent replies.
177
183
 
178
- NOTE: when this is set, agent can ONLY reply and CANNOT use any tools, such as
179
- function tools, RAGs, agent transfer, etc.
184
+ NOTE:
185
+ When this is set, agent can ONLY reply and CANNOT use any tools, such as
186
+ function tools, RAGs, agent transfer, etc.
180
187
  """
181
188
  output_key: Optional[str] = None
182
189
  """The key in session state to store the output of the agent.
@@ -191,9 +198,9 @@ class LlmAgent(BaseAgent):
191
198
  planner: Optional[BasePlanner] = None
192
199
  """Instructs the agent to make a plan and execute it step by step.
193
200
 
194
- NOTE: to use model's built-in thinking features, set the `thinking_config`
195
- field in `google.adk.planners.built_in_planner`.
196
-
201
+ NOTE:
202
+ To use model's built-in thinking features, set the `thinking_config`
203
+ field in `google.adk.planners.built_in_planner`.
197
204
  """
198
205
 
199
206
  code_executor: Optional[BaseCodeExecutor] = None
@@ -202,7 +209,8 @@ class LlmAgent(BaseAgent):
202
209
 
203
210
  Check out available code executions in `google.adk.code_executor` package.
204
211
 
205
- NOTE: to use model's built-in code executor, use the `BuiltInCodeExecutor`.
212
+ NOTE:
213
+ To use model's built-in code executor, use the `BuiltInCodeExecutor`.
206
214
  """
207
215
  # Advance features - End
208
216
 
@@ -516,5 +524,178 @@ class LlmAgent(BaseAgent):
516
524
  )
517
525
  return generate_content_config
518
526
 
527
+ @classmethod
528
+ @working_in_progress('LlmAgent._resolve_tools is not ready for use.')
529
+ def _resolve_tools(cls, tools_config: list[CodeConfig]) -> list[Any]:
530
+ """Resolve tools from configuration.
531
+
532
+ Args:
533
+ tools_config: List of tool configurations (CodeConfig objects).
534
+
535
+ Returns:
536
+ List of resolved tool objects.
537
+ """
538
+
539
+ resolved_tools = []
540
+ for tool_config in tools_config:
541
+ if '.' not in tool_config.name:
542
+ module = importlib.import_module('google.adk.tools')
543
+ obj = getattr(module, tool_config.name)
544
+ if isinstance(obj, ToolUnion):
545
+ resolved_tools.append(obj)
546
+ else:
547
+ raise ValueError(
548
+ f'Invalid tool name: {tool_config.name} is not a built-in tool.'
549
+ )
550
+ else:
551
+ from .config_agent_utils import resolve_code_reference
552
+
553
+ resolved_tools.append(resolve_code_reference(tool_config))
554
+
555
+ return resolved_tools
556
+
557
+ @classmethod
558
+ @override
559
+ @working_in_progress('LlmAgent.from_config is not ready for use.')
560
+ def from_config(
561
+ cls: Type[LlmAgent],
562
+ config: LlmAgentConfig,
563
+ config_abs_path: str,
564
+ ) -> LlmAgent:
565
+ from .config_agent_utils import resolve_callbacks
566
+
567
+ agent = super().from_config(config, config_abs_path)
568
+ if config.model:
569
+ agent.model = config.model
570
+ if config.instruction:
571
+ agent.instruction = config.instruction
572
+ if config.disallow_transfer_to_parent:
573
+ agent.disallow_transfer_to_parent = config.disallow_transfer_to_parent
574
+ if config.disallow_transfer_to_peers:
575
+ agent.disallow_transfer_to_peers = config.disallow_transfer_to_peers
576
+ if config.include_contents != 'default':
577
+ agent.include_contents = config.include_contents
578
+ if config.output_key:
579
+ agent.output_key = config.output_key
580
+ if config.tools:
581
+ agent.tools = cls._resolve_tools(config.tools)
582
+ if config.before_model_callbacks:
583
+ agent.before_model_callback = resolve_callbacks(
584
+ config.before_model_callbacks
585
+ )
586
+ if config.after_model_callbacks:
587
+ agent.after_model_callback = resolve_callbacks(
588
+ config.after_model_callbacks
589
+ )
590
+ if config.before_tool_callbacks:
591
+ agent.before_tool_callback = resolve_callbacks(
592
+ config.before_tool_callbacks
593
+ )
594
+ if config.after_tool_callbacks:
595
+ agent.after_tool_callback = resolve_callbacks(config.after_tool_callbacks)
596
+ return agent
597
+
519
598
 
520
599
  Agent: TypeAlias = LlmAgent
600
+
601
+
602
+ class LlmAgentConfig(BaseAgentConfig):
603
+ """The config for the YAML schema of a LlmAgent."""
604
+
605
+ agent_class: Literal['LlmAgent', ''] = 'LlmAgent'
606
+ """The value is used to uniquely identify the LlmAgent class. If it is
607
+ empty, it is by default an LlmAgent."""
608
+
609
+ model: Optional[str] = None
610
+ """Optional. LlmAgent.model. If not set, the model will be inherited from
611
+ the ancestor."""
612
+
613
+ instruction: str
614
+ """Required. LlmAgent.instruction."""
615
+
616
+ disallow_transfer_to_parent: Optional[bool] = None
617
+ """Optional. LlmAgent.disallow_transfer_to_parent."""
618
+
619
+ disallow_transfer_to_peers: Optional[bool] = None
620
+ """Optional. LlmAgent.disallow_transfer_to_peers."""
621
+
622
+ output_key: Optional[str] = None
623
+ """Optional. LlmAgent.output_key."""
624
+
625
+ include_contents: Literal['default', 'none'] = 'default'
626
+ """Optional. LlmAgent.include_contents."""
627
+
628
+ tools: Optional[list[CodeConfig]] = None
629
+ """Optional. LlmAgent.tools.
630
+
631
+ Examples:
632
+
633
+ For ADK built-in tools in `google.adk.tools` package, they can be referenced
634
+ directly with the name:
635
+
636
+ ```
637
+ tools:
638
+ - name: google_search
639
+ - name: load_memory
640
+ ```
641
+
642
+ For user-defined tools, they can be referenced with fully qualified name:
643
+
644
+ ```
645
+ tools:
646
+ - name: my_library.my_tools.my_tool
647
+ ```
648
+
649
+ For tools that needs to be created via functions:
650
+
651
+ ```
652
+ tools:
653
+ - name: my_library.my_tools.create_tool
654
+ args:
655
+ - name: param1
656
+ value: value1
657
+ - name: param2
658
+ value: value2
659
+ ```
660
+
661
+ For more advanced tools, instead of specifying arguments in config, it's
662
+ recommended to define them in Python files and reference them. E.g.,
663
+
664
+ ```
665
+ # tools.py
666
+ my_mcp_toolset = MCPToolset(
667
+ connection_params=StdioServerParameters(
668
+ command="npx",
669
+ args=["-y", "@notionhq/notion-mcp-server"],
670
+ env={"OPENAPI_MCP_HEADERS": NOTION_HEADERS},
671
+ )
672
+ )
673
+ ```
674
+
675
+ Then, reference the toolset in config:
676
+
677
+ ```
678
+ tools:
679
+ - name: tools.my_mcp_toolset
680
+ ```
681
+ """
682
+
683
+ before_model_callbacks: Optional[List[CodeConfig]] = None
684
+ """Optional. LlmAgent.before_model_callbacks.
685
+
686
+ Example:
687
+
688
+ ```
689
+ before_model_callbacks:
690
+ - name: my_library.callbacks.before_model_callback
691
+ ```
692
+ """
693
+
694
+ after_model_callbacks: Optional[List[CodeConfig]] = None
695
+ """Optional. LlmAgent.after_model_callbacks."""
696
+
697
+ before_tool_callbacks: Optional[List[CodeConfig]] = None
698
+ """Optional. LlmAgent.before_tool_callbacks."""
699
+
700
+ after_tool_callbacks: Optional[List[CodeConfig]] = None
701
+ """Optional. LlmAgent.after_tool_callbacks."""
@@ -16,14 +16,20 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
+ from typing import Any
19
20
  from typing import AsyncGenerator
21
+ from typing import Dict
22
+ from typing import Literal
20
23
  from typing import Optional
24
+ from typing import Type
21
25
 
22
26
  from typing_extensions import override
23
27
 
24
28
  from ..agents.invocation_context import InvocationContext
25
29
  from ..events.event import Event
30
+ from ..utils.feature_decorator import working_in_progress
26
31
  from .base_agent import BaseAgent
32
+ from .base_agent import BaseAgentConfig
27
33
 
28
34
 
29
35
  class LoopAgent(BaseAgent):
@@ -60,3 +66,26 @@ class LoopAgent(BaseAgent):
60
66
  ) -> AsyncGenerator[Event, None]:
61
67
  raise NotImplementedError('This is not supported yet for LoopAgent.')
62
68
  yield # AsyncGenerator requires having at least one yield statement
69
+
70
+ @classmethod
71
+ @override
72
+ @working_in_progress('LoopAgent.from_config is not ready for use.')
73
+ def from_config(
74
+ cls: Type[LoopAgent],
75
+ config: LoopAgentConfig,
76
+ config_abs_path: str,
77
+ ) -> LoopAgent:
78
+ agent = super().from_config(config, config_abs_path)
79
+ if config.max_iterations:
80
+ agent.max_iterations = config.max_iterations
81
+ return agent
82
+
83
+
84
+ @working_in_progress('LoopAgentConfig is not ready for use.')
85
+ class LoopAgentConfig(BaseAgentConfig):
86
+ """The config for the YAML schema of a LoopAgent."""
87
+
88
+ agent_class: Literal['LoopAgent'] = 'LoopAgent'
89
+
90
+ max_iterations: Optional[int] = None
91
+ """Optional. LoopAgent.max_iterations."""
@@ -18,9 +18,13 @@ from __future__ import annotations
18
18
 
19
19
  import asyncio
20
20
  from typing import AsyncGenerator
21
+ from typing import Literal
22
+ from typing import Type
21
23
 
22
24
  from typing_extensions import override
23
25
 
26
+ from ..agents.base_agent import BaseAgentConfig
27
+ from ..agents.base_agent import working_in_progress
24
28
  from ..agents.invocation_context import InvocationContext
25
29
  from ..events.event import Event
26
30
  from .base_agent import BaseAgent
@@ -33,9 +37,9 @@ def _create_branch_ctx_for_sub_agent(
33
37
  ) -> InvocationContext:
34
38
  """Create isolated branch for every sub-agent."""
35
39
  invocation_context = invocation_context.model_copy()
36
- branch_suffix = f"{agent.name}.{sub_agent.name}"
40
+ branch_suffix = f'{agent.name}.{sub_agent.name}'
37
41
  invocation_context.branch = (
38
- f"{invocation_context.branch}.{branch_suffix}"
42
+ f'{invocation_context.branch}.{branch_suffix}'
39
43
  if invocation_context.branch
40
44
  else branch_suffix
41
45
  )
@@ -109,5 +113,22 @@ class ParallelAgent(BaseAgent):
109
113
  async def _run_live_impl(
110
114
  self, ctx: InvocationContext
111
115
  ) -> AsyncGenerator[Event, None]:
112
- raise NotImplementedError("This is not supported yet for ParallelAgent.")
116
+ raise NotImplementedError('This is not supported yet for ParallelAgent.')
113
117
  yield # AsyncGenerator requires having at least one yield statement
118
+
119
+ @classmethod
120
+ @override
121
+ @working_in_progress('ParallelAgent.from_config is not ready for use.')
122
+ def from_config(
123
+ cls: Type[ParallelAgent],
124
+ config: ParallelAgentConfig,
125
+ config_abs_path: str,
126
+ ) -> ParallelAgent:
127
+ return super().from_config(config, config_abs_path)
128
+
129
+
130
+ @working_in_progress('ParallelAgentConfig is not ready for use.')
131
+ class ParallelAgentConfig(BaseAgentConfig):
132
+ """The config for the YAML schema of a ParallelAgent."""
133
+
134
+ agent_class: Literal['ParallelAgent'] = 'ParallelAgent'
@@ -26,7 +26,7 @@ import uuid
26
26
 
27
27
  try:
28
28
  from a2a.client import A2AClient
29
- from a2a.client.client import A2ACardResolver # Import A2ACardResolver
29
+ from a2a.client.client import A2ACardResolver
30
30
  from a2a.types import AgentCard
31
31
  from a2a.types import Message as A2AMessage
32
32
  from a2a.types import MessageSendParams as A2AMessageSendParams
@@ -35,7 +35,6 @@ try:
35
35
  from a2a.types import SendMessageRequest
36
36
  from a2a.types import SendMessageSuccessResponse
37
37
  from a2a.types import Task as A2ATask
38
-
39
38
  except ImportError as e:
40
39
  import sys
41
40
 
@@ -46,6 +45,12 @@ except ImportError as e:
46
45
  else:
47
46
  raise e
48
47
 
48
+ try:
49
+ from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH
50
+ except ImportError:
51
+ # Fallback for older versions of a2a-sdk.
52
+ AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent.json"
53
+
49
54
  from google.genai import types as genai_types
50
55
  import httpx
51
56
 
@@ -63,11 +68,18 @@ from ..flows.llm_flows.functions import find_matching_function_call
63
68
  from ..utils.feature_decorator import experimental
64
69
  from .base_agent import BaseAgent
65
70
 
71
+ __all__ = [
72
+ "A2AClientError",
73
+ "AGENT_CARD_WELL_KNOWN_PATH",
74
+ "AgentCardResolutionError",
75
+ "RemoteA2aAgent",
76
+ ]
77
+
78
+
66
79
  # Constants
67
80
  A2A_METADATA_PREFIX = "a2a:"
68
81
  DEFAULT_TIMEOUT = 600.0
69
82
 
70
-
71
83
  logger = logging.getLogger("google_adk." + __name__)
72
84
 
73
85
 
@@ -17,9 +17,13 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  from typing import AsyncGenerator
20
+ from typing import Literal
21
+ from typing import Type
20
22
 
21
23
  from typing_extensions import override
22
24
 
25
+ from ..agents.base_agent import BaseAgentConfig
26
+ from ..agents.base_agent import working_in_progress
23
27
  from ..agents.invocation_context import InvocationContext
24
28
  from ..events.event import Event
25
29
  from .base_agent import BaseAgent
@@ -60,7 +64,7 @@ class SequentialAgent(BaseAgent):
60
64
  Signals that the model has successfully completed the user's question
61
65
  or task.
62
66
  """
63
- return "Task completion signaled."
67
+ return 'Task completion signaled.'
64
68
 
65
69
  if isinstance(sub_agent, LlmAgent):
66
70
  # Use function name to dedupe.
@@ -74,3 +78,20 @@ class SequentialAgent(BaseAgent):
74
78
  for sub_agent in self.sub_agents:
75
79
  async for event in sub_agent.run_live(ctx):
76
80
  yield event
81
+
82
+ @classmethod
83
+ @override
84
+ @working_in_progress('SequentialAgent.from_config is not ready for use.')
85
+ def from_config(
86
+ cls: Type[SequentialAgent],
87
+ config: SequentialAgentConfig,
88
+ config_abs_path: str,
89
+ ) -> SequentialAgent:
90
+ return super().from_config(config, config_abs_path)
91
+
92
+
93
+ @working_in_progress('SequentialAgentConfig is not ready for use.')
94
+ class SequentialAgentConfig(BaseAgentConfig):
95
+ """The config for the YAML schema of a SequentialAgent."""
96
+
97
+ agent_class: Literal['SequentialAgent'] = 'SequentialAgent'
@@ -12,7 +12,14 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """An artifact service implementation using Google Cloud Storage (GCS)."""
15
+ """An artifact service implementation using Google Cloud Storage (GCS).
16
+
17
+ The blob name format used depends on whether the filename has a user namespace:
18
+ - For files with user namespace (starting with "user:"):
19
+ {app_name}/{user_id}/user/{filename}/{version}
20
+ - For regular session-scoped files:
21
+ {app_name}/{user_id}/{session_id}/{filename}/{version}
22
+ """
16
23
  from __future__ import annotations
17
24
 
18
25
  import logging
@@ -187,10 +194,25 @@ class GcsArtifactService(BaseArtifactService):
187
194
  async def list_versions(
188
195
  self, *, app_name: str, user_id: str, session_id: str, filename: str
189
196
  ) -> list[int]:
197
+ """Lists all available versions of an artifact.
198
+
199
+ This method retrieves all versions of a specific artifact by querying GCS blobs
200
+ that match the constructed blob name prefix.
201
+
202
+ Args:
203
+ app_name: The name of the application.
204
+ user_id: The ID of the user who owns the artifact.
205
+ session_id: The ID of the session (ignored for user-namespaced files).
206
+ filename: The name of the artifact file.
207
+
208
+ Returns:
209
+ A list of version numbers (integers) available for the specified artifact.
210
+ Returns an empty list if no versions are found.
211
+ """
190
212
  prefix = self._get_blob_name(app_name, user_id, session_id, filename, "")
191
213
  blobs = self.storage_client.list_blobs(self.bucket, prefix=prefix)
192
214
  versions = []
193
215
  for blob in blobs:
194
- _, _, _, _, version = blob.name.split("/")
216
+ *_, version = blob.name.split("/")
195
217
  versions.append(int(version))
196
218
  return versions
@@ -30,9 +30,9 @@ if TYPE_CHECKING:
30
30
  try:
31
31
  from authlib.integrations.requests_client import OAuth2Session
32
32
 
33
- AUTHLIB_AVIALABLE = True
33
+ AUTHLIB_AVAILABLE = True
34
34
  except ImportError:
35
- AUTHLIB_AVIALABLE = False
35
+ AUTHLIB_AVAILABLE = False
36
36
 
37
37
 
38
38
  class AuthHandler:
@@ -146,7 +146,7 @@ class AuthHandler:
146
146
  ValueError: If the authorization endpoint is not configured in the auth
147
147
  scheme.
148
148
  """
149
- if not AUTHLIB_AVIALABLE:
149
+ if not AUTHLIB_AVAILABLE:
150
150
  return (
151
151
  self.auth_config.raw_auth_credential.model_copy(deep=True)
152
152
  if self.auth_config.raw_auth_credential
@@ -16,7 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  from typing import Optional
18
18
 
19
- from ..tools.tool_context import ToolContext
19
+ from ..agents.callback_context import CallbackContext
20
20
  from ..utils.feature_decorator import experimental
21
21
  from .auth_credential import AuthCredential
22
22
  from .auth_credential import AuthCredentialTypes
@@ -63,7 +63,7 @@ class CredentialManager:
63
63
  )
64
64
 
65
65
  # Load and prepare credential
66
- credential = await manager.load_auth_credential(tool_context)
66
+ credential = await manager.load_auth_credential(callback_context)
67
67
  ```
68
68
  """
69
69
 
@@ -100,11 +100,11 @@ class CredentialManager:
100
100
  """
101
101
  self._exchanger_registry.register(credential_type, exchanger_instance)
102
102
 
103
- async def request_credential(self, tool_context: ToolContext) -> None:
104
- tool_context.request_credential(self._auth_config)
103
+ async def request_credential(self, callback_context: CallbackContext) -> None:
104
+ callback_context.request_credential(self._auth_config)
105
105
 
106
106
  async def get_auth_credential(
107
- self, tool_context: ToolContext
107
+ self, callback_context: CallbackContext
108
108
  ) -> Optional[AuthCredential]:
109
109
  """Load and prepare authentication credential through a structured workflow."""
110
110
 
@@ -116,14 +116,14 @@ class CredentialManager:
116
116
  return self._auth_config.raw_auth_credential
117
117
 
118
118
  # Step 3: Try to load existing processed credential
119
- credential = await self._load_existing_credential(tool_context)
119
+ credential = await self._load_existing_credential(callback_context)
120
120
 
121
121
  # Step 4: If no existing credential, load from auth response
122
122
  # TODO instead of load from auth response, we can store auth response in
123
123
  # credential service.
124
124
  was_from_auth_response = False
125
125
  if not credential:
126
- credential = await self._load_from_auth_response(tool_context)
126
+ credential = await self._load_from_auth_response(callback_context)
127
127
  was_from_auth_response = True
128
128
 
129
129
  # Step 5: If still no credential available, return None
@@ -134,22 +134,23 @@ class CredentialManager:
134
134
  credential, was_exchanged = await self._exchange_credential(credential)
135
135
 
136
136
  # Step 7: Refresh credential if expired
137
+ was_refreshed = False
137
138
  if not was_exchanged:
138
139
  credential, was_refreshed = await self._refresh_credential(credential)
139
140
 
140
141
  # Step 8: Save credential if it was modified
141
142
  if was_from_auth_response or was_exchanged or was_refreshed:
142
- await self._save_credential(tool_context, credential)
143
+ await self._save_credential(callback_context, credential)
143
144
 
144
145
  return credential
145
146
 
146
147
  async def _load_existing_credential(
147
- self, tool_context: ToolContext
148
+ self, callback_context: CallbackContext
148
149
  ) -> Optional[AuthCredential]:
149
150
  """Load existing credential from credential service or cached exchanged credential."""
150
151
 
151
152
  # Try loading from credential service first
152
- credential = await self._load_from_credential_service(tool_context)
153
+ credential = await self._load_from_credential_service(callback_context)
153
154
  if credential:
154
155
  return credential
155
156
 
@@ -160,23 +161,21 @@ class CredentialManager:
160
161
  return None
161
162
 
162
163
  async def _load_from_credential_service(
163
- self, tool_context: ToolContext
164
+ self, callback_context: CallbackContext
164
165
  ) -> Optional[AuthCredential]:
165
166
  """Load credential from credential service if available."""
166
- credential_service = tool_context._invocation_context.credential_service
167
+ credential_service = callback_context._invocation_context.credential_service
167
168
  if credential_service:
168
169
  # Note: This should be made async in a future refactor
169
170
  # For now, assuming synchronous operation
170
- return await credential_service.load_credential(
171
- self._auth_config, tool_context
172
- )
171
+ return await callback_context.load_credential(self._auth_config)
173
172
  return None
174
173
 
175
174
  async def _load_from_auth_response(
176
- self, tool_context: ToolContext
175
+ self, callback_context: CallbackContext
177
176
  ) -> Optional[AuthCredential]:
178
- """Load credential from auth response in tool context."""
179
- return tool_context.get_auth_response(self._auth_config)
177
+ """Load credential from auth response in callback context."""
178
+ return callback_context.get_auth_response(self._auth_config)
180
179
 
181
180
  async def _exchange_credential(
182
181
  self, credential: AuthCredential
@@ -251,11 +250,12 @@ class CredentialManager:
251
250
  # Additional validation can be added here
252
251
 
253
252
  async def _save_credential(
254
- self, tool_context: ToolContext, credential: AuthCredential
253
+ self, callback_context: CallbackContext, credential: AuthCredential
255
254
  ) -> None:
256
255
  """Save credential to credential service if available."""
257
- credential_service = tool_context._invocation_context.credential_service
256
+ # Update the exchanged credential in config
257
+ self._auth_config.exchanged_auth_credential = credential
258
+
259
+ credential_service = callback_context._invocation_context.credential_service
258
260
  if credential_service:
259
- # Update the exchanged credential in config
260
- self._auth_config.exchanged_auth_credential = credential
261
- await credential_service.save_credential(self._auth_config, tool_context)
261
+ await callback_context.save_credential(self._auth_config)
@@ -18,7 +18,7 @@ from abc import ABC
18
18
  from abc import abstractmethod
19
19
  from typing import Optional
20
20
 
21
- from ...tools.tool_context import ToolContext
21
+ from ...agents.callback_context import CallbackContext
22
22
  from ...utils.feature_decorator import experimental
23
23
  from ..auth_credential import AuthCredential
24
24
  from ..auth_tool import AuthConfig
@@ -33,10 +33,10 @@ class BaseCredentialService(ABC):
33
33
  async def load_credential(
34
34
  self,
35
35
  auth_config: AuthConfig,
36
- tool_context: ToolContext,
36
+ callback_context: CallbackContext,
37
37
  ) -> Optional[AuthCredential]:
38
38
  """
39
- Loads the credential by auth config and current tool context from the
39
+ Loads the credential by auth config and current callback context from the
40
40
  backend credential store.
41
41
 
42
42
  Args:
@@ -44,7 +44,7 @@ class BaseCredentialService(ABC):
44
44
  credential information. auth_config.get_credential_key will be used to
45
45
  build the key to load the credential.
46
46
 
47
- tool_context: The context of the current invocation when the tool is
47
+ callback_context: The context of the current invocation when the tool is
48
48
  trying to load the credential.
49
49
 
50
50
  Returns:
@@ -56,7 +56,7 @@ class BaseCredentialService(ABC):
56
56
  async def save_credential(
57
57
  self,
58
58
  auth_config: AuthConfig,
59
- tool_context: ToolContext,
59
+ callback_context: CallbackContext,
60
60
  ) -> None:
61
61
  """
62
62
  Saves the exchanged_auth_credential in auth config to the backend credential
@@ -67,7 +67,7 @@ class BaseCredentialService(ABC):
67
67
  credential information. auth_config.get_credential_key will be used to
68
68
  build the key to save the credential.
69
69
 
70
- tool_context: The context of the current invocation when the tool is
70
+ callback_context: The context of the current invocation when the tool is
71
71
  trying to save the credential.
72
72
 
73
73
  Returns: