veadk-python 0.2.27__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 (218) hide show
  1. veadk/__init__.py +37 -0
  2. veadk/a2a/__init__.py +13 -0
  3. veadk/a2a/agent_card.py +45 -0
  4. veadk/a2a/remote_ve_agent.py +390 -0
  5. veadk/a2a/utils/__init__.py +13 -0
  6. veadk/a2a/utils/agent_to_a2a.py +170 -0
  7. veadk/a2a/ve_a2a_server.py +93 -0
  8. veadk/a2a/ve_agent_executor.py +78 -0
  9. veadk/a2a/ve_middlewares.py +313 -0
  10. veadk/a2a/ve_task_store.py +37 -0
  11. veadk/agent.py +402 -0
  12. veadk/agent_builder.py +93 -0
  13. veadk/agents/loop_agent.py +68 -0
  14. veadk/agents/parallel_agent.py +72 -0
  15. veadk/agents/sequential_agent.py +64 -0
  16. veadk/auth/__init__.py +13 -0
  17. veadk/auth/base_auth.py +22 -0
  18. veadk/auth/ve_credential_service.py +203 -0
  19. veadk/auth/veauth/__init__.py +13 -0
  20. veadk/auth/veauth/apmplus_veauth.py +58 -0
  21. veadk/auth/veauth/ark_veauth.py +75 -0
  22. veadk/auth/veauth/base_veauth.py +50 -0
  23. veadk/auth/veauth/cozeloop_veauth.py +13 -0
  24. veadk/auth/veauth/opensearch_veauth.py +75 -0
  25. veadk/auth/veauth/postgresql_veauth.py +75 -0
  26. veadk/auth/veauth/prompt_pilot_veauth.py +60 -0
  27. veadk/auth/veauth/speech_veauth.py +54 -0
  28. veadk/auth/veauth/utils.py +69 -0
  29. veadk/auth/veauth/vesearch_veauth.py +62 -0
  30. veadk/auth/veauth/viking_mem0_veauth.py +91 -0
  31. veadk/cli/__init__.py +13 -0
  32. veadk/cli/cli.py +58 -0
  33. veadk/cli/cli_clean.py +87 -0
  34. veadk/cli/cli_create.py +163 -0
  35. veadk/cli/cli_deploy.py +233 -0
  36. veadk/cli/cli_eval.py +215 -0
  37. veadk/cli/cli_init.py +214 -0
  38. veadk/cli/cli_kb.py +110 -0
  39. veadk/cli/cli_pipeline.py +285 -0
  40. veadk/cli/cli_prompt.py +86 -0
  41. veadk/cli/cli_update.py +106 -0
  42. veadk/cli/cli_uploadevalset.py +139 -0
  43. veadk/cli/cli_web.py +143 -0
  44. veadk/cloud/__init__.py +13 -0
  45. veadk/cloud/cloud_agent_engine.py +485 -0
  46. veadk/cloud/cloud_app.py +475 -0
  47. veadk/config.py +115 -0
  48. veadk/configs/__init__.py +13 -0
  49. veadk/configs/auth_configs.py +133 -0
  50. veadk/configs/database_configs.py +132 -0
  51. veadk/configs/model_configs.py +78 -0
  52. veadk/configs/tool_configs.py +54 -0
  53. veadk/configs/tracing_configs.py +110 -0
  54. veadk/consts.py +74 -0
  55. veadk/evaluation/__init__.py +17 -0
  56. veadk/evaluation/adk_evaluator/__init__.py +17 -0
  57. veadk/evaluation/adk_evaluator/adk_evaluator.py +302 -0
  58. veadk/evaluation/base_evaluator.py +642 -0
  59. veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
  60. veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +339 -0
  61. veadk/evaluation/eval_set_file_loader.py +48 -0
  62. veadk/evaluation/eval_set_recorder.py +146 -0
  63. veadk/evaluation/types.py +65 -0
  64. veadk/evaluation/utils/prometheus.py +196 -0
  65. veadk/integrations/__init__.py +13 -0
  66. veadk/integrations/ve_apig/__init__.py +13 -0
  67. veadk/integrations/ve_apig/ve_apig.py +349 -0
  68. veadk/integrations/ve_apig/ve_apig_utils.py +332 -0
  69. veadk/integrations/ve_code_pipeline/__init__.py +13 -0
  70. veadk/integrations/ve_code_pipeline/ve_code_pipeline.py +431 -0
  71. veadk/integrations/ve_cozeloop/__init__.py +13 -0
  72. veadk/integrations/ve_cozeloop/ve_cozeloop.py +96 -0
  73. veadk/integrations/ve_cr/__init__.py +13 -0
  74. veadk/integrations/ve_cr/ve_cr.py +220 -0
  75. veadk/integrations/ve_faas/__init__.py +13 -0
  76. veadk/integrations/ve_faas/template/cookiecutter.json +15 -0
  77. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
  78. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
  79. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/config.yaml.example +6 -0
  80. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/deploy.py +106 -0
  81. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__init__.py +13 -0
  82. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/agent.py +25 -0
  83. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +202 -0
  84. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/requirements.txt +3 -0
  85. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +49 -0
  86. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/__init__.py +14 -0
  87. veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/agent.py +27 -0
  88. veadk/integrations/ve_faas/ve_faas.py +754 -0
  89. veadk/integrations/ve_faas/ve_faas_utils.py +408 -0
  90. veadk/integrations/ve_faas/web_template/cookiecutter.json +20 -0
  91. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
  92. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
  93. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example +2 -0
  94. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py +44 -0
  95. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile +23 -0
  96. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py +123 -0
  97. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py +46 -0
  98. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py +36 -0
  99. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt +4 -0
  100. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh +21 -0
  101. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css +368 -0
  102. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js +0 -0
  103. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html +21 -0
  104. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html +24 -0
  105. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html +21 -0
  106. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html +53 -0
  107. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html +45 -0
  108. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html +29 -0
  109. veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html +14 -0
  110. veadk/integrations/ve_identity/__init__.py +110 -0
  111. veadk/integrations/ve_identity/auth_config.py +261 -0
  112. veadk/integrations/ve_identity/auth_mixins.py +650 -0
  113. veadk/integrations/ve_identity/auth_processor.py +385 -0
  114. veadk/integrations/ve_identity/function_tool.py +158 -0
  115. veadk/integrations/ve_identity/identity_client.py +864 -0
  116. veadk/integrations/ve_identity/mcp_tool.py +181 -0
  117. veadk/integrations/ve_identity/mcp_toolset.py +431 -0
  118. veadk/integrations/ve_identity/models.py +228 -0
  119. veadk/integrations/ve_identity/token_manager.py +188 -0
  120. veadk/integrations/ve_identity/utils.py +151 -0
  121. veadk/integrations/ve_prompt_pilot/__init__.py +13 -0
  122. veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +85 -0
  123. veadk/integrations/ve_tls/__init__.py +13 -0
  124. veadk/integrations/ve_tls/utils.py +116 -0
  125. veadk/integrations/ve_tls/ve_tls.py +212 -0
  126. veadk/integrations/ve_tos/ve_tos.py +710 -0
  127. veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
  128. veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +308 -0
  129. veadk/knowledgebase/__init__.py +17 -0
  130. veadk/knowledgebase/backends/__init__.py +13 -0
  131. veadk/knowledgebase/backends/base_backend.py +72 -0
  132. veadk/knowledgebase/backends/in_memory_backend.py +91 -0
  133. veadk/knowledgebase/backends/opensearch_backend.py +162 -0
  134. veadk/knowledgebase/backends/redis_backend.py +172 -0
  135. veadk/knowledgebase/backends/utils.py +92 -0
  136. veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +608 -0
  137. veadk/knowledgebase/entry.py +25 -0
  138. veadk/knowledgebase/knowledgebase.py +307 -0
  139. veadk/memory/__init__.py +35 -0
  140. veadk/memory/long_term_memory.py +365 -0
  141. veadk/memory/long_term_memory_backends/__init__.py +13 -0
  142. veadk/memory/long_term_memory_backends/base_backend.py +35 -0
  143. veadk/memory/long_term_memory_backends/in_memory_backend.py +67 -0
  144. veadk/memory/long_term_memory_backends/mem0_backend.py +155 -0
  145. veadk/memory/long_term_memory_backends/opensearch_backend.py +124 -0
  146. veadk/memory/long_term_memory_backends/redis_backend.py +140 -0
  147. veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +189 -0
  148. veadk/memory/short_term_memory.py +252 -0
  149. veadk/memory/short_term_memory_backends/__init__.py +13 -0
  150. veadk/memory/short_term_memory_backends/base_backend.py +31 -0
  151. veadk/memory/short_term_memory_backends/mysql_backend.py +49 -0
  152. veadk/memory/short_term_memory_backends/postgresql_backend.py +49 -0
  153. veadk/memory/short_term_memory_backends/sqlite_backend.py +55 -0
  154. veadk/memory/short_term_memory_processor.py +100 -0
  155. veadk/processors/__init__.py +26 -0
  156. veadk/processors/base_run_processor.py +120 -0
  157. veadk/prompts/__init__.py +13 -0
  158. veadk/prompts/agent_default_prompt.py +30 -0
  159. veadk/prompts/prompt_evaluator.py +20 -0
  160. veadk/prompts/prompt_memory_processor.py +55 -0
  161. veadk/prompts/prompt_optimization.py +150 -0
  162. veadk/runner.py +732 -0
  163. veadk/tools/__init__.py +13 -0
  164. veadk/tools/builtin_tools/__init__.py +13 -0
  165. veadk/tools/builtin_tools/agent_authorization.py +94 -0
  166. veadk/tools/builtin_tools/generate_image.py +23 -0
  167. veadk/tools/builtin_tools/image_edit.py +300 -0
  168. veadk/tools/builtin_tools/image_generate.py +446 -0
  169. veadk/tools/builtin_tools/lark.py +67 -0
  170. veadk/tools/builtin_tools/las.py +24 -0
  171. veadk/tools/builtin_tools/link_reader.py +66 -0
  172. veadk/tools/builtin_tools/llm_shield.py +381 -0
  173. veadk/tools/builtin_tools/load_knowledgebase.py +97 -0
  174. veadk/tools/builtin_tools/mcp_router.py +29 -0
  175. veadk/tools/builtin_tools/run_code.py +113 -0
  176. veadk/tools/builtin_tools/tts.py +253 -0
  177. veadk/tools/builtin_tools/vesearch.py +49 -0
  178. veadk/tools/builtin_tools/video_generate.py +363 -0
  179. veadk/tools/builtin_tools/web_scraper.py +76 -0
  180. veadk/tools/builtin_tools/web_search.py +83 -0
  181. veadk/tools/demo_tools.py +58 -0
  182. veadk/tools/load_knowledgebase_tool.py +149 -0
  183. veadk/tools/sandbox/__init__.py +13 -0
  184. veadk/tools/sandbox/browser_sandbox.py +37 -0
  185. veadk/tools/sandbox/code_sandbox.py +40 -0
  186. veadk/tools/sandbox/computer_sandbox.py +34 -0
  187. veadk/tracing/__init__.py +13 -0
  188. veadk/tracing/base_tracer.py +58 -0
  189. veadk/tracing/telemetry/__init__.py +13 -0
  190. veadk/tracing/telemetry/attributes/attributes.py +29 -0
  191. veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +180 -0
  192. veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +858 -0
  193. veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +152 -0
  194. veadk/tracing/telemetry/attributes/extractors/types.py +164 -0
  195. veadk/tracing/telemetry/exporters/__init__.py +13 -0
  196. veadk/tracing/telemetry/exporters/apmplus_exporter.py +558 -0
  197. veadk/tracing/telemetry/exporters/base_exporter.py +39 -0
  198. veadk/tracing/telemetry/exporters/cozeloop_exporter.py +129 -0
  199. veadk/tracing/telemetry/exporters/inmemory_exporter.py +248 -0
  200. veadk/tracing/telemetry/exporters/tls_exporter.py +139 -0
  201. veadk/tracing/telemetry/opentelemetry_tracer.py +320 -0
  202. veadk/tracing/telemetry/telemetry.py +411 -0
  203. veadk/types.py +47 -0
  204. veadk/utils/__init__.py +13 -0
  205. veadk/utils/audio_manager.py +95 -0
  206. veadk/utils/auth.py +294 -0
  207. veadk/utils/logger.py +59 -0
  208. veadk/utils/mcp_utils.py +44 -0
  209. veadk/utils/misc.py +184 -0
  210. veadk/utils/patches.py +101 -0
  211. veadk/utils/volcengine_sign.py +205 -0
  212. veadk/version.py +15 -0
  213. veadk_python-0.2.27.dist-info/METADATA +373 -0
  214. veadk_python-0.2.27.dist-info/RECORD +218 -0
  215. veadk_python-0.2.27.dist-info/WHEEL +5 -0
  216. veadk_python-0.2.27.dist-info/entry_points.txt +2 -0
  217. veadk_python-0.2.27.dist-info/licenses/LICENSE +201 -0
  218. veadk_python-0.2.27.dist-info/top_level.txt +1 -0
@@ -0,0 +1,650 @@
1
+ # Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
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
+ """
16
+ Authentication mixins for Identity integration.
17
+
18
+ These mixins provide reusable authentication logic that can be mixed into
19
+ different tool classes to avoid code duplication.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import urllib.parse
25
+ import asyncio
26
+ from typing import Any, Callable, List, Literal, Optional
27
+ from abc import ABC, abstractmethod
28
+
29
+ from google.adk.auth.auth_credential import (
30
+ AuthCredential,
31
+ AuthCredentialTypes,
32
+ OAuth2Auth,
33
+ )
34
+ from google.adk.auth.auth_tool import AuthConfig
35
+ from google.adk.auth.auth_handler import AuthHandler
36
+ from google.adk.tools.tool_context import ToolContext
37
+ from google.adk.tools.openapi_tool.auth.auth_helpers import dict_to_auth_scheme
38
+ from google.adk.agents.readonly_context import ReadonlyContext
39
+
40
+ from veadk.integrations.ve_identity.models import OAuth2AuthPoller, OAuth2TokenResponse
41
+ from veadk.integrations.ve_identity.identity_client import IdentityClient
42
+ from veadk.integrations.ve_identity.auth_config import (
43
+ VeIdentityAuthConfig,
44
+ ApiKeyAuthConfig,
45
+ OAuth2AuthConfig,
46
+ WorkloadAuthConfig,
47
+ get_default_identity_client,
48
+ )
49
+ from veadk.integrations.ve_identity.token_manager import get_workload_token
50
+
51
+ from veadk.utils.logger import get_logger
52
+
53
+ logger = get_logger(__name__)
54
+
55
+
56
+ # OAuth2 scheme definition (shared across all OAuth2 tools)
57
+ oauth2_scheme = dict_to_auth_scheme(
58
+ {
59
+ "type": "oauth2",
60
+ "flows": {
61
+ "authorizationCode": {
62
+ "authorizationUrl": "__EMPTY__",
63
+ "tokenUrl": "__EMPTY__",
64
+ }
65
+ },
66
+ }
67
+ )
68
+
69
+
70
+ class AuthRequiredException(Exception):
71
+ """Exception raised when user authorization is required for OAuth2 flow."""
72
+
73
+ def __init__(self, message: str):
74
+ self.message = message
75
+ super().__init__(message)
76
+
77
+
78
+ class BaseAuthMixin(ABC):
79
+ """Base mixin for Identity authentication.
80
+
81
+ This mixin provides common functionality for all Identity authentication types.
82
+ Specific authentication implementations should inherit from this class.
83
+ """
84
+
85
+ def __init__(
86
+ self,
87
+ *,
88
+ provider_name: str,
89
+ identity_client: Optional[IdentityClient] = None,
90
+ region: Optional[str] = None,
91
+ **kwargs,
92
+ ):
93
+ """Initialize the Identity authentication mixin.
94
+
95
+ Args:
96
+ provider_name: Name of the credential provider configured in identity service.
97
+ identity_client: Optional IdentityClient instance. If not provided, creates a new one.
98
+ region: VolcEngine region for the identity client. If not provided, uses the region
99
+ from VeADK config. Defaults to "cn-beijing" if config is not available.
100
+ **kwargs: Additional arguments passed to parent classes.
101
+ """
102
+ # Only pass kwargs to super() if we're in a multiple inheritance scenario
103
+ # and the next class in MRO expects them
104
+ try:
105
+ super().__init__(**kwargs)
106
+ except TypeError:
107
+ # If super().__init__() doesn't accept kwargs (e.g., object.__init__),
108
+ # call it without arguments
109
+ super().__init__()
110
+
111
+ self._identity_client = identity_client or get_default_identity_client(region)
112
+ self._provider_name = provider_name
113
+
114
+ @abstractmethod
115
+ async def _get_credential(
116
+ self, *, tool_context: ToolContext | ReadonlyContext
117
+ ) -> AuthCredential:
118
+ """Get or create authentication credential.
119
+
120
+ Args:
121
+ tool_context: The tool context for accessing session state.
122
+
123
+ Returns:
124
+ The authentication credential.
125
+ """
126
+ pass
127
+
128
+ @abstractmethod
129
+ async def _execute_with_credential(
130
+ self,
131
+ *,
132
+ args: dict[str, Any],
133
+ tool_context: ToolContext | ReadonlyContext,
134
+ credential: AuthCredential,
135
+ ) -> Any:
136
+ """Execute the tool with the provided credential.
137
+
138
+ Args:
139
+ args: Arguments to pass to the tool.
140
+ tool_context: The tool context.
141
+ credential: The authentication credential.
142
+
143
+ Returns:
144
+ The result from the tool execution.
145
+ """
146
+ pass
147
+
148
+ async def run_with_identity_auth(
149
+ self,
150
+ *,
151
+ args: dict[str, Any],
152
+ tool_context: ToolContext | ReadonlyContext,
153
+ ) -> Any:
154
+ """Execute the tool with Identity authentication.
155
+
156
+ This is the main entry point that handles the authentication flow
157
+ and delegates to the specific implementation.
158
+
159
+ Args:
160
+ args: Arguments to pass to the tool.
161
+ tool_context: The tool context.
162
+
163
+ Returns:
164
+ The result from the tool execution.
165
+ """
166
+ credential = await self._get_credential(tool_context=tool_context)
167
+ return await self._execute_with_credential(
168
+ args=args, tool_context=tool_context, credential=credential
169
+ )
170
+
171
+
172
+ class ApiKeyAuthMixin(BaseAuthMixin):
173
+ """Mixin for API key authentication via Identity Service.
174
+
175
+ This mixin handles:
176
+ 1. Retrieving workload access tokens for the agent
177
+ 2. Fetching API keys from the identity service
178
+ 3. Caching API keys in session state
179
+ """
180
+
181
+ async def _get_credential(
182
+ self, *, tool_context: ToolContext | ReadonlyContext
183
+ ) -> AuthCredential:
184
+ """Get or create API key credential.
185
+
186
+ Args:
187
+ tool_context: The tool context for accessing session state.
188
+
189
+ Returns:
190
+ The API key credential.
191
+ """
192
+ # Build cache key for the API key
193
+ credential_key = (
194
+ f"api_key:{tool_context.agent_name}:"
195
+ f"{tool_context._invocation_context.user_id}:{self._provider_name}"
196
+ )
197
+
198
+ # Try to get cached API key
199
+ credential: AuthCredential = tool_context._invocation_context.session.state.get(
200
+ credential_key
201
+ )
202
+
203
+ # Fetch API key if not cached
204
+ if not credential or credential.api_key is None:
205
+ # Get workload access token for the agent
206
+ workload_token = await get_workload_token(
207
+ tool_context=tool_context,
208
+ identity_client=self._identity_client,
209
+ )
210
+
211
+ # Fetch API key from identity service
212
+ api_key = self._identity_client.get_api_key(
213
+ provider_name=self._provider_name,
214
+ agent_identity_token=workload_token,
215
+ )
216
+
217
+ # Create and cache the credential
218
+ credential = AuthCredential(
219
+ auth_type=AuthCredentialTypes.API_KEY,
220
+ api_key=api_key,
221
+ )
222
+ tool_context._invocation_context.session.state[credential_key] = credential
223
+
224
+ return credential
225
+
226
+ async def _execute_with_credential(
227
+ self,
228
+ *,
229
+ args: dict[str, Any],
230
+ tool_context: ToolContext | ReadonlyContext,
231
+ credential: AuthCredential,
232
+ ) -> Any:
233
+ """Default implementation - should be overridden by concrete tool classes.
234
+
235
+ This provides a base implementation to satisfy the abstract method requirement.
236
+ Concrete tool classes should override this method with their specific logic.
237
+
238
+ Args:
239
+ args: Arguments to pass to the tool.
240
+ tool_context: The tool context.
241
+ credential: The authentication credential.
242
+
243
+ Returns:
244
+ The result from the tool execution.
245
+
246
+ Raises:
247
+ NotImplementedError: Always, as this should be overridden.
248
+ """
249
+ raise NotImplementedError(
250
+ f"{self.__class__.__name__} must override _execute_with_credential method"
251
+ )
252
+
253
+
254
+ class WorkloadAuthMixin(BaseAuthMixin):
255
+ """Mixin for Workload Access Token authentication via Identity Service.
256
+
257
+ This mixin handles:
258
+ 1. Retrieving workload access tokens for the agent
259
+ 2. Fetching API keys from the identity service
260
+ 3. Caching API keys in session state
261
+ """
262
+
263
+ async def _get_credential(
264
+ self, *, tool_context: ToolContext | ReadonlyContext
265
+ ) -> AuthCredential:
266
+ """Get or create Workload Access Token credential.
267
+
268
+ Args:
269
+ tool_context: The tool context for accessing session state.
270
+
271
+ Returns:
272
+ The Workload Access Token credential.
273
+ """
274
+ # Build cache key for the Workload Access Token
275
+ credential_key = (
276
+ f"workload_access_token:{tool_context.agent_name}:"
277
+ f"{tool_context._invocation_context.user_id}:{self._provider_name}"
278
+ )
279
+
280
+ # Try to get cached Workload Access Token
281
+ credential: AuthCredential = tool_context._invocation_context.session.state.get(
282
+ credential_key
283
+ )
284
+
285
+ # Fetch Workload Access Token if not cached
286
+ if not credential or credential.api_key is None:
287
+ # Get workload access token for the agent
288
+ workload_access_token = await get_workload_token(
289
+ tool_context=tool_context,
290
+ identity_client=self._identity_client,
291
+ )
292
+
293
+ # Create and cache the credential
294
+ credential = AuthCredential(
295
+ auth_type=AuthCredentialTypes.OAUTH2,
296
+ oauth2=OAuth2Auth(access_token=workload_access_token),
297
+ )
298
+ tool_context._invocation_context.session.state[credential_key] = credential
299
+
300
+ return credential
301
+
302
+ async def _execute_with_credential(
303
+ self,
304
+ *,
305
+ args: dict[str, Any],
306
+ tool_context: ToolContext | ReadonlyContext,
307
+ credential: AuthCredential,
308
+ ) -> Any:
309
+ """Default implementation - should be overridden by concrete tool classes.
310
+
311
+ This provides a base implementation to satisfy the abstract method requirement.
312
+ Concrete tool classes should override this method with their specific logic.
313
+
314
+ Args:
315
+ args: Arguments to pass to the tool.
316
+ tool_context: The tool context.
317
+ credential: The authentication credential.
318
+
319
+ Returns:
320
+ The result from the tool execution.
321
+
322
+ Raises:
323
+ NotImplementedError: Always, as this should be overridden.
324
+ """
325
+ raise NotImplementedError(
326
+ f"{self.__class__.__name__} must override _execute_with_credential method"
327
+ )
328
+
329
+
330
+ class OAuth2AuthMixin(BaseAuthMixin):
331
+ """Mixin for OAuth2 authentication via Identity Service.
332
+
333
+ This mixin handles:
334
+ 1. Retrieving workload access tokens for the agent
335
+ 2. Requesting OAuth2 tokens from the identity service
336
+ 3. Handling user authorization flows when needed
337
+ """
338
+
339
+ def __init__(
340
+ self,
341
+ *,
342
+ scopes: Optional[List[str]] = None,
343
+ auth_flow: Optional[Literal["M2M", "USER_FEDERATION"]] = None,
344
+ callback_url: Optional[str] = None,
345
+ force_authentication: bool = False,
346
+ response_for_auth_required: Optional[str] = None,
347
+ on_auth_url: Optional[Callable[[str], Any]] = None,
348
+ # Currently we only use auth_uri to initialize poller, may extend to support other fields like exchanged_auth_credential.
349
+ oauth2_auth_poller: Optional[Callable[[Any], OAuth2AuthPoller]] = None,
350
+ **kwargs,
351
+ ):
352
+ """Initialize the OAuth2 authentication mixin.
353
+
354
+ Args:
355
+ scopes: Optional list of OAuth2 scopes to request. If not provided,
356
+ the control plane will use the default configured scopes.
357
+ auth_flow: Optional authentication flow type - "M2M" for machine-to-machine or
358
+ "USER_FEDERATION" for user-delegated access. If not provided,
359
+ the control plane will use the default configured flow.
360
+ callback_url: OAuth2 redirect URL (must be pre-registered).
361
+ force_authentication: If True, forces re-authentication even if cached token exists.
362
+ response_for_auth_required: Custom response to return when user authorization is needed.
363
+ If None, returns "Pending User Authorization.".
364
+ **kwargs: Additional arguments passed to parent classes.
365
+ """
366
+ super().__init__(**kwargs)
367
+ self._scopes = scopes
368
+ self._auth_flow = auth_flow
369
+ self._callback_url = callback_url
370
+ self._force_authentication = force_authentication
371
+ self._response_for_auth_required = response_for_auth_required
372
+ self._on_auth_url = on_auth_url
373
+ self._oauth2_auth_poller = oauth2_auth_poller
374
+
375
+ async def _get_oauth2_token_or_auth_url(
376
+ self, *, tool_context: ToolContext | ReadonlyContext
377
+ ) -> OAuth2TokenResponse:
378
+ """Retrieve OAuth2 token or authorization URL from identity service.
379
+
380
+ Args:
381
+ tool_context: The tool context for accessing session state and user info.
382
+
383
+ Returns:
384
+ Dictionary with either a token or authorization URL.
385
+ """
386
+ # Get workload access token for the agent
387
+ workload_token = await get_workload_token(
388
+ tool_context=tool_context,
389
+ identity_client=self._identity_client,
390
+ )
391
+
392
+ # Request OAuth2 token or auth URL
393
+ return self._identity_client.get_oauth2_token_or_auth_url(
394
+ provider_name=self._provider_name,
395
+ agent_identity_token=workload_token,
396
+ auth_flow=self._auth_flow,
397
+ scopes=self._scopes,
398
+ callback_url=self._callback_url,
399
+ force_authentication=self._force_authentication,
400
+ )
401
+
402
+ async def _get_credential(
403
+ self, *, tool_context: ToolContext | ReadonlyContext
404
+ ) -> AuthCredential:
405
+ """Get or create OAuth2 credential.
406
+
407
+ This method handles the OAuth2 flow and may raise an exception
408
+ if user authorization is required.
409
+
410
+ Args:
411
+ tool_context: The tool context for accessing session state.
412
+
413
+ Returns:
414
+ The OAuth2 credential.
415
+
416
+ Raises:
417
+ AuthRequiredException: If user authorization is required.
418
+ """
419
+ auth_config = AuthConfig(
420
+ auth_scheme=oauth2_scheme,
421
+ credential_key=f"oauth_access_token:{tool_context.agent_name}:{tool_context._invocation_context.user_id}:{self._provider_name}",
422
+ )
423
+
424
+ # Check if we already have a credential from previous auth
425
+ if credential := AuthHandler(auth_config).get_auth_response(tool_context.state):
426
+ # Use existing credential
427
+ return credential
428
+
429
+ # No credential yet - request token or auth URL
430
+ response = await self._get_oauth2_token_or_auth_url(tool_context=tool_context)
431
+
432
+ if response.response_type == "auth_url":
433
+ # Need user authorization
434
+ auth_uri = urllib.parse.unquote(response.authorization_url)
435
+ if isinstance(tool_context, ToolContext):
436
+ # For ToolContext, use the standard request_credential flow
437
+ auth_config.raw_auth_credential = AuthCredential(
438
+ auth_type=AuthCredentialTypes.OAUTH2,
439
+ oauth2=OAuth2Auth(auth_uri=auth_uri),
440
+ resource_ref=response.resource_ref,
441
+ )
442
+ tool_context.request_credential(auth_config=auth_config) # type: ignore
443
+
444
+ # Raise a special exception to indicate auth is required
445
+ raise AuthRequiredException(
446
+ self._response_for_auth_required or "Pending User Authorization."
447
+ )
448
+ else:
449
+ # For ReadonlyContext (e.g., in get_tools), handle OAuth2 flow directly
450
+ return await self._handle_oauth2_flow_for_readonly_context(
451
+ auth_uri=auth_uri,
452
+ resource_ref=response.resource_ref,
453
+ readonly_context=tool_context,
454
+ )
455
+ else:
456
+ # Got token directly - create credential
457
+ return AuthCredential(
458
+ auth_type=AuthCredentialTypes.OAUTH2,
459
+ oauth2=OAuth2Auth(access_token=response.access_token),
460
+ )
461
+
462
+ async def _handle_oauth2_flow_for_readonly_context(
463
+ self,
464
+ *,
465
+ auth_uri: str,
466
+ resource_ref: Optional[str],
467
+ readonly_context: ReadonlyContext,
468
+ ) -> AuthCredential:
469
+ """Handle OAuth2 flow for ReadonlyContext (e.g., during get_tools).
470
+
471
+ This method implements a direct OAuth2 flow similar to auth_processor.py
472
+ but adapted for ReadonlyContext scenarios.
473
+
474
+ Args:
475
+ auth_uri: The OAuth2 authorization URI
476
+ resource_ref: Resource reference for the OAuth2 request
477
+ tool_context: The readonly context
478
+
479
+ Returns:
480
+ AuthCredential with OAuth2 access token
481
+ """
482
+ import json
483
+
484
+ # Parse resource_ref if available
485
+ request_dict = json.loads(resource_ref) if resource_ref else {}
486
+
487
+ # Use custom on_auth_url callback if provided
488
+ if on_auth_url := self._on_auth_url:
489
+ if asyncio.iscoroutinefunction(on_auth_url):
490
+ await on_auth_url(auth_uri)
491
+ else:
492
+ on_auth_url(auth_uri)
493
+ else:
494
+ logger.info(f"Authorization required: {auth_uri}")
495
+
496
+ # Use custom oauth2_auth_poller if provided
497
+ if oauth2_auth_poller := self._oauth2_auth_poller:
498
+ active_poller = oauth2_auth_poller(auth_uri, request_dict)
499
+ else:
500
+ # Use default poller
501
+ active_poller = self._create_default_oauth2_poller(auth_uri, request_dict)
502
+
503
+ # Poll for the OAuth2 auth
504
+ updated_oauth2_auth = await active_poller.poll_for_auth()
505
+
506
+ if not updated_oauth2_auth or not updated_oauth2_auth.access_token:
507
+ raise AuthRequiredException("Failed to obtain OAuth2 access token")
508
+
509
+ return AuthCredential(
510
+ auth_type=AuthCredentialTypes.OAUTH2,
511
+ oauth2=updated_oauth2_auth,
512
+ )
513
+
514
+ def _create_default_oauth2_poller(self, auth_uri: str, request_dict: dict):
515
+ """Create a default OAuth2 poller for ReadonlyContext scenarios."""
516
+ from veadk.integrations.ve_identity.auth_processor import (
517
+ _DefaultOauth2AuthPoller,
518
+ )
519
+
520
+ async def async_token_fetcher():
521
+ response = self._identity_client.get_oauth2_token_or_auth_url(
522
+ **request_dict
523
+ )
524
+ return (
525
+ OAuth2Auth(access_token=response.access_token)
526
+ if response.access_token and response.access_token.strip()
527
+ else None
528
+ )
529
+
530
+ return _DefaultOauth2AuthPoller(auth_uri, async_token_fetcher)
531
+
532
+ async def _execute_with_credential(
533
+ self,
534
+ *,
535
+ args: dict[str, Any],
536
+ tool_context: ToolContext | ReadonlyContext,
537
+ credential: AuthCredential,
538
+ ) -> Any:
539
+ """Default implementation - should be overridden by concrete tool classes.
540
+
541
+ This provides a base implementation to satisfy the abstract method requirement.
542
+ Concrete tool classes should override this method with their specific logic.
543
+
544
+ Args:
545
+ args: Arguments to pass to the tool.
546
+ tool_context: The tool context.
547
+ credential: The authentication credential.
548
+
549
+ Returns:
550
+ The result from the tool execution.
551
+
552
+ Raises:
553
+ NotImplementedError: Always, as this should be overridden.
554
+ """
555
+ raise NotImplementedError(
556
+ f"{self.__class__.__name__} must override _execute_with_credential method"
557
+ )
558
+
559
+
560
+ class VeIdentityAuthMixin(BaseAuthMixin):
561
+ """Unified mixin that supports both API Key and OAuth2 authentication based on configuration.
562
+
563
+ This mixin uses composition to delegate to specific authentication implementations
564
+ while providing a unified interface for tools.
565
+ """
566
+
567
+ def __init__(self, *, auth_config: VeIdentityAuthConfig, **kwargs):
568
+ """Initialize the unified authentication mixin.
569
+
570
+ Args:
571
+ auth_config: Authentication configuration (ApiKeyAuthConfig, WorkloadAuthConfig, or OAuth2AuthConfig).
572
+ **kwargs: Additional arguments passed to parent classes.
573
+ """
574
+ # Initialize base class with common parameters
575
+ super().__init__(
576
+ provider_name=auth_config.provider_name,
577
+ identity_client=auth_config.identity_client,
578
+ region=auth_config.region,
579
+ **kwargs,
580
+ )
581
+ self._auth_config = auth_config
582
+
583
+ # Create the appropriate authentication delegate based on config type
584
+ self._auth_delegate = self._create_auth_delegate(**kwargs)
585
+
586
+ def _create_auth_delegate(self, **kwargs):
587
+ """Create the appropriate authentication delegate based on config type."""
588
+ if isinstance(self._auth_config, ApiKeyAuthConfig):
589
+ return ApiKeyAuthMixin(
590
+ provider_name=self._auth_config.provider_name,
591
+ identity_client=self._auth_config.identity_client,
592
+ region=self._auth_config.region,
593
+ **kwargs,
594
+ )
595
+ elif isinstance(self._auth_config, WorkloadAuthConfig):
596
+ return WorkloadAuthMixin(
597
+ provider_name=self._auth_config.provider_name,
598
+ identity_client=self._auth_config.identity_client,
599
+ region=self._auth_config.region,
600
+ **kwargs,
601
+ )
602
+ elif isinstance(self._auth_config, OAuth2AuthConfig):
603
+ return OAuth2AuthMixin(
604
+ provider_name=self._auth_config.provider_name,
605
+ scopes=self._auth_config.scopes,
606
+ auth_flow=self._auth_config.auth_flow,
607
+ callback_url=self._auth_config.callback_url,
608
+ force_authentication=self._auth_config.force_authentication,
609
+ response_for_auth_required=self._auth_config.response_for_auth_required,
610
+ on_auth_url=self._auth_config.on_auth_url,
611
+ oauth2_auth_poller=self._auth_config.oauth2_auth_poller,
612
+ identity_client=self._auth_config.identity_client,
613
+ region=self._auth_config.region,
614
+ **kwargs,
615
+ )
616
+ else:
617
+ raise ValueError(f"Unsupported auth config type: {type(self._auth_config)}")
618
+
619
+ async def _get_credential(
620
+ self, *, tool_context: ToolContext | ReadonlyContext
621
+ ) -> AuthCredential:
622
+ """Get or create authentication credential using the configured auth type."""
623
+ return await self._auth_delegate._get_credential(tool_context=tool_context)
624
+
625
+ async def _execute_with_credential(
626
+ self,
627
+ *,
628
+ args: dict[str, Any],
629
+ tool_context: ToolContext | ReadonlyContext,
630
+ credential: AuthCredential,
631
+ ) -> Any:
632
+ """Default implementation - should be overridden by concrete tool classes.
633
+
634
+ This provides a base implementation to satisfy the abstract method requirement.
635
+ Concrete tool classes should override this method with their specific logic.
636
+
637
+ Args:
638
+ args: Arguments to pass to the tool.
639
+ tool_context: The tool context.
640
+ credential: The authentication credential.
641
+
642
+ Returns:
643
+ The result from the tool execution.
644
+
645
+ Raises:
646
+ NotImplementedError: Always, as this should be overridden.
647
+ """
648
+ raise NotImplementedError(
649
+ f"{self.__class__.__name__} must override _execute_with_credential method"
650
+ )