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,385 @@
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
+ """Authentication request processor for handling OAuth2 flows in agent conversations."""
16
+
17
+ from __future__ import annotations
18
+
19
+ import asyncio
20
+ import json
21
+ import time
22
+ from typing import Any, Awaitable, Callable, Optional, TYPE_CHECKING
23
+
24
+ from google.adk.auth import AuthConfig
25
+ from google.genai import types
26
+ from google.adk.auth.auth_credential import OAuth2Auth
27
+
28
+ from veadk.integrations.ve_identity.auth_config import get_default_identity_client
29
+ from veadk.processors.base_run_processor import BaseRunProcessor
30
+ from veadk.integrations.ve_identity.models import AuthRequestConfig, OAuth2AuthPoller
31
+ from veadk.integrations.ve_identity.utils import (
32
+ get_function_call_auth_config,
33
+ get_function_call_id,
34
+ is_pending_auth_event,
35
+ )
36
+ from a2a.utils import new_agent_text_message
37
+
38
+ from veadk.utils.logger import get_logger
39
+ from a2a.server.tasks import TaskUpdater
40
+ from a2a.types import TaskState
41
+
42
+
43
+ if TYPE_CHECKING:
44
+ from veadk.runner import Runner
45
+
46
+
47
+ logger = get_logger(__name__)
48
+
49
+ # Default configuration for token polling
50
+ DEFAULT_POLLING_INTERVAL_SECONDS = 5
51
+ DEFAULT_POLLING_TIMEOUT_SECONDS = 600
52
+ # Authentication loop will break after this many cycles
53
+ DEFAULT_MAX_CYCLES = 10
54
+
55
+
56
+ class MockOauth2AuthPoller(OAuth2AuthPoller):
57
+ """Mock OAuth2 auth poller for testing purposes."""
58
+
59
+ async def poll_for_auth(self, *args, **kwargs) -> OAuth2Auth:
60
+ """Return a mock oauth2 auth immediately.
61
+
62
+ Returns:
63
+ A mock token string.
64
+ """
65
+ return OAuth2Auth(access_token="mock_token")
66
+
67
+
68
+ class WaitInputOauth2AuthPoller(OAuth2AuthPoller):
69
+ """Wait for user input to complete OAuth2 flow."""
70
+
71
+ def __init__(self, auth_uri: str, *args, **kwargs):
72
+ self.auth_uri = auth_uri
73
+
74
+ async def poll_for_auth(self) -> OAuth2Auth:
75
+ """Wait for user input to complete OAuth2 flow.
76
+
77
+ Returns:
78
+ The complete OAuth2Auth object containing auth_response_uri.
79
+ """
80
+
81
+ async def get_user_input(prompt: str) -> str:
82
+ loop = asyncio.get_event_loop()
83
+ # Run the blocking `input()` function in a separate thread managed by the executor.
84
+ return await loop.run_in_executor(None, input, prompt)
85
+
86
+ logger.info(
87
+ f"Please open this URL in your browser to authorize: {self.auth_uri}"
88
+ )
89
+ auth_response_uri = await get_user_input("Please enter the callback URL:\n> ")
90
+ auth_response_uri = auth_response_uri.strip()
91
+
92
+ if not auth_response_uri:
93
+ raise Exception("No auth response URI provided. Aborting.")
94
+
95
+ return OAuth2Auth(auth_response_uri=auth_response_uri)
96
+
97
+
98
+ class _DefaultOauth2AuthPoller(OAuth2AuthPoller):
99
+ """Default implementation of OAuth2 auth polling.
100
+
101
+ This poller repeatedly calls a polling function until a OAuth2 auth becomes available
102
+ or a timeout occurs.
103
+ """
104
+
105
+ def __init__(
106
+ self,
107
+ auth_url: str,
108
+ polling_func: (
109
+ Callable[[], Optional[OAuth2Auth]]
110
+ | Callable[[], Awaitable[Optional[OAuth2Auth]]]
111
+ ),
112
+ ):
113
+ """Initialize the OAuth2 auth poller.
114
+
115
+ Args:
116
+ auth_url: The authorization URL for logging purposes.
117
+ polling_func: Function that returns the OAuth2 auth when ready, or None if not yet available.
118
+ Can be either sync or async function.
119
+ """
120
+ self.auth_url = auth_url
121
+ self.polling_func = polling_func
122
+
123
+ async def poll_for_auth(self) -> OAuth2Auth:
124
+ """Poll for a oauth2 auth until it becomes available or timeout occurs.
125
+
126
+ Returns:
127
+ The complete OAuth2Auth object containing tokens or auth_response_uri.
128
+
129
+ Raises:
130
+ asyncio.TimeoutError: If polling times out before oauth2 auth is available.
131
+ """
132
+ start_time = time.time()
133
+
134
+ while time.time() - start_time < DEFAULT_POLLING_TIMEOUT_SECONDS:
135
+ await asyncio.sleep(DEFAULT_POLLING_INTERVAL_SECONDS)
136
+
137
+ logger.info(
138
+ f"Polling for oauth2 auth at authorization URL: {self.auth_url}"
139
+ )
140
+
141
+ # Check if polling_func is async or sync
142
+ import inspect
143
+
144
+ if inspect.iscoroutinefunction(self.polling_func):
145
+ oauth2auth = await self.polling_func()
146
+ else:
147
+ oauth2auth = self.polling_func()
148
+
149
+ if oauth2auth is not None:
150
+ logger.info("OAuth2 auth successfully retrieved")
151
+ return oauth2auth
152
+
153
+ raise asyncio.TimeoutError(
154
+ f"OAuth2 auth polling timed out after {DEFAULT_POLLING_TIMEOUT_SECONDS} seconds. "
155
+ "User may not have completed authorization."
156
+ )
157
+
158
+
159
+ class AuthRequestProcessor(BaseRunProcessor):
160
+ """Processor for handling authentication requests in agent conversations.
161
+
162
+ This class manages the OAuth2 authentication flow when tools require user authorization.
163
+ It handles displaying authorization URLs to users and polling for tokens after authorization.
164
+
165
+ Attributes:
166
+ config: Configuration for authentication request handling.
167
+ """
168
+
169
+ def __init__(self, *, config: Optional[AuthRequestConfig] = None):
170
+ """Initialize the authentication request processor.
171
+
172
+ Args:
173
+ config: Authentication configuration. If None, uses default configuration
174
+ that prints the authorization URL to console.
175
+ """
176
+ self.config = config or AuthRequestConfig(
177
+ on_auth_url=lambda url: print(
178
+ f"Please open this URL in your browser to authorize: {url}"
179
+ )
180
+ )
181
+
182
+ self._identity_client = (
183
+ self.config.identity_client
184
+ or get_default_identity_client(self.config.region)
185
+ )
186
+
187
+ async def process_auth_request(
188
+ self,
189
+ auth_request_event_id: str,
190
+ auth_config: AuthConfig,
191
+ task_updater: Optional[TaskUpdater] = None,
192
+ ) -> types.Content:
193
+ """Process a single authentication request.
194
+
195
+ This method handles the complete OAuth2 flow:
196
+ 1. Displays the authorization URL to the user (via on_auth_url callback)
197
+ 2. Polls for the access token after user authorization
198
+ 3. Constructs and returns the authentication response
199
+
200
+ Args:
201
+ auth_request_event_id: Unique ID of the authentication request event.
202
+ auth_config: Authentication configuration containing the auth URI.
203
+
204
+ Returns:
205
+ Content object containing the authentication response to send back to the agent.
206
+ """
207
+ logger.info(f"Processing auth request: {auth_request_event_id}")
208
+
209
+ auth_uri = auth_config.exchanged_auth_credential.oauth2.auth_uri
210
+ request_dict = (
211
+ json.loads(resource_ref_str)
212
+ if (resource_ref_str := auth_config.exchanged_auth_credential.resource_ref)
213
+ else {}
214
+ )
215
+
216
+ # Invoke the auth URL callback (sync or async)
217
+ if on_auth_url := self.config.on_auth_url:
218
+ if asyncio.iscoroutinefunction(on_auth_url):
219
+ await on_auth_url(auth_uri)
220
+ else:
221
+ on_auth_url(auth_uri)
222
+
223
+ # Use custom poller or default poller
224
+ active_poller = (
225
+ self.config.oauth2_auth_poller(auth_uri, request_dict)
226
+ if self.config.oauth2_auth_poller
227
+ else _DefaultOauth2AuthPoller(
228
+ auth_uri,
229
+ lambda: (
230
+ lambda response: (
231
+ OAuth2Auth(access_token=response.access_token)
232
+ if response.access_token and response.access_token.strip()
233
+ else None
234
+ )
235
+ )(self._identity_client.get_oauth2_token_or_auth_url(**request_dict)),
236
+ )
237
+ )
238
+
239
+ # Poll for the oauth2 auth
240
+ updated_oauth2_auth = await active_poller.poll_for_auth()
241
+ if task_updater:
242
+ await task_updater.update_status(
243
+ TaskState.working,
244
+ message=new_agent_text_message("Authorization received, continuing..."),
245
+ )
246
+ for k, v in updated_oauth2_auth.__dict__.items():
247
+ if (
248
+ v is not None
249
+ and k
250
+ in auth_config.exchanged_auth_credential.oauth2.__pydantic_fields__
251
+ ):
252
+ auth_config.exchanged_auth_credential.oauth2.__dict__[k] = v
253
+
254
+ # Construct the authentication response
255
+ auth_content = types.Content(
256
+ role="user",
257
+ parts=[
258
+ types.Part(
259
+ function_response=types.FunctionResponse(
260
+ id=auth_request_event_id,
261
+ name="adk_request_credential",
262
+ response=auth_config.model_dump(),
263
+ )
264
+ )
265
+ ],
266
+ )
267
+
268
+ logger.info(f"Auth request {auth_request_event_id} processed successfully")
269
+ return auth_content
270
+
271
+ def process_run(
272
+ self,
273
+ runner: Runner,
274
+ message: types.Content,
275
+ **kwargs: Any,
276
+ ):
277
+ """Process the agent run by wrapping the event generator with authentication loop.
278
+
279
+ This method implements the BaseRunProcessor interface and adds authentication
280
+ loop handling to event generators.
281
+
282
+ This decorator intercepts runner.run_async calls and automatically handles
283
+ authentication loops. The event_generator code can remain completely unchanged!
284
+
285
+ Usage example:
286
+ @auth_processor.process_run(
287
+ runner=runner,
288
+ message=message,
289
+ )
290
+ async def event_generator():
291
+ async for event in runner.run_async(
292
+ user_id=user_id,
293
+ session_id=session_id,
294
+ new_message=message,
295
+ run_config=RunConfig(streaming_mode=stream_mode),
296
+ ):
297
+ if event.get_function_calls():
298
+ for function_call in event.get_function_calls():
299
+ logger.debug(f"Function call: {function_call}")
300
+ elif event.content is not None:
301
+ yield event.content.parts[0].text
302
+
303
+ # Then call (no parameters needed):
304
+ async for chunk in event_generator():
305
+ print(chunk)
306
+
307
+ The decorator automatically:
308
+ 1. Intercepts runner.run_async calls
309
+ 2. Detects authentication events
310
+ 3. Processes authentication requests
311
+ 4. Loops until no more authentication events
312
+
313
+ Args:
314
+ runner: Runner instance (will be wrapped).
315
+ message: Initial message to send.
316
+ **kwargs: Additional keyword arguments. Supports:
317
+ - task_updater: Optional TaskUpdater for status updates.
318
+
319
+ Returns:
320
+ Decorated generator function.
321
+ """
322
+ # Extract task_updater from kwargs
323
+ task_updater = kwargs.get("task_updater")
324
+
325
+ def decorator(event_generator_func):
326
+ async def wrapper():
327
+ current_message = message
328
+
329
+ for _ in range(self.config.max_auth_cycles or DEFAULT_MAX_CYCLES):
330
+ auth_request_event_id = None
331
+ auth_config = None
332
+
333
+ # Buffer to collect chunks from this cycle
334
+ cycle_buffer = []
335
+
336
+ # Create a wrapped runner to intercept run_async calls
337
+ original_run_async = runner.run_async
338
+
339
+ async def wrapped_run_async(**run_kwargs):
340
+ nonlocal auth_request_event_id, auth_config
341
+
342
+ # Override the message with the current message
343
+ run_kwargs["new_message"] = current_message
344
+
345
+ async for event in original_run_async(**run_kwargs):
346
+ # Detect authentication events
347
+ if is_pending_auth_event(event):
348
+ auth_request_event_id = get_function_call_id(event)
349
+ auth_config = get_function_call_auth_config(event)
350
+ logger.info(
351
+ f"Found auth request {auth_request_event_id}, breaking to process"
352
+ )
353
+
354
+ break
355
+
356
+ # Pass events to the caller
357
+ yield event
358
+
359
+ # Temporarily replace runner.run_async
360
+ runner.run_async = wrapped_run_async
361
+
362
+ try:
363
+ # Call the original event_generator and buffer the output
364
+ async for chunk in event_generator_func():
365
+ cycle_buffer.append(chunk)
366
+ finally:
367
+ # Restore the original run_async
368
+ runner.run_async = original_run_async
369
+
370
+ # Check if there was an authentication request
371
+ if auth_request_event_id and auth_config:
372
+ # Process the authentication request
373
+ current_message = await self.process_auth_request(
374
+ auth_request_event_id, auth_config, task_updater
375
+ )
376
+ # Continue to next cycle
377
+ else:
378
+ # No more auth events, yield the final chunk
379
+ logger.info("No more auth events found, processing complete")
380
+ yield cycle_buffer[-1]
381
+ break
382
+
383
+ return wrapper
384
+
385
+ return decorator
@@ -0,0 +1,158 @@
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
+ from __future__ import annotations
16
+
17
+ import inspect
18
+ from typing import Any, Callable
19
+
20
+ from typing_extensions import override
21
+
22
+ from google.adk.auth.auth_credential import AuthCredential
23
+ from google.adk.tools.function_tool import FunctionTool
24
+ from google.adk.tools.tool_context import ToolContext
25
+
26
+ from veadk.integrations.ve_identity.auth_config import (
27
+ VeIdentityAuthConfig,
28
+ ApiKeyAuthConfig,
29
+ OAuth2AuthConfig,
30
+ WorkloadAuthConfig,
31
+ )
32
+ from veadk.integrations.ve_identity.auth_mixins import (
33
+ VeIdentityAuthMixin,
34
+ AuthRequiredException,
35
+ )
36
+
37
+ from veadk.utils.logger import get_logger
38
+
39
+ logger = get_logger(__name__)
40
+
41
+
42
+ class VeIdentityFunctionTool(VeIdentityAuthMixin, FunctionTool):
43
+ """Unified function tool with automatic VeIdentity authentication.
44
+
45
+ This tool wraps a function and automatically handles authentication based on the
46
+ provided auth configuration. It supports both API Key and OAuth2 authentication.
47
+
48
+ Examples:
49
+ # API Key authentication
50
+ api_key_tool = VeIdentityFunctionTool(
51
+ func=my_function,
52
+ auth_config=api_key_auth("my-provider"),
53
+ into="api_key"
54
+ )
55
+
56
+ # OAuth2 authentication
57
+ oauth2_tool = VeIdentityFunctionTool(
58
+ func=my_function,
59
+ auth_config=oauth2_auth(
60
+ provider_name="my-provider",
61
+ scopes=["read", "write"],
62
+ auth_flow="USER_FEDERATION"
63
+ ),
64
+ into="access_token"
65
+ )
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ *,
71
+ func: Callable[..., Any],
72
+ auth_config: VeIdentityAuthConfig,
73
+ into: str = None,
74
+ ):
75
+ """Initialize the unified Identity function tool.
76
+
77
+ Args:
78
+ func: The function to wrap with Identity authentication.
79
+ auth_config: Authentication configuration (ApiKeyAuthConfig or OAuth2AuthConfig).
80
+ into: Parameter name to inject the credential into. If None, uses default
81
+ based on auth type ("api_key" for API key, "access_token" for OAuth2).
82
+ """
83
+ # Determine default parameter name based on auth type
84
+ if into is None:
85
+ if isinstance(auth_config, ApiKeyAuthConfig):
86
+ into = "api_key"
87
+ elif isinstance(auth_config, OAuth2AuthConfig):
88
+ into = "access_token"
89
+ elif isinstance(auth_config, WorkloadAuthConfig):
90
+ into = "access_token"
91
+ else:
92
+ raise ValueError(f"Unsupported auth config type: {type(auth_config)}")
93
+
94
+ # Initialize mixins first
95
+ super().__init__(
96
+ func=func,
97
+ auth_config=auth_config,
98
+ )
99
+ self._ignore_params.append(into)
100
+ self._into = into
101
+
102
+ @override
103
+ async def run_async(
104
+ self, *, args: dict[str, Any], tool_context: ToolContext
105
+ ) -> Any:
106
+ """Execute the wrapped function with Identity authentication.
107
+
108
+ This method handles authentication based on the configured auth type.
109
+
110
+ Args:
111
+ args: Arguments to pass to the wrapped function.
112
+ tool_context: The tool context for accessing session state and auth.
113
+
114
+ Returns:
115
+ The result from the wrapped function, or an auth pending message for OAuth2.
116
+ """
117
+ try:
118
+ return await self.run_with_identity_auth(
119
+ args=args, tool_context=tool_context
120
+ )
121
+ except AuthRequiredException as e:
122
+ # Only OAuth2 can raise this exception
123
+ return e.message
124
+
125
+ async def _execute_with_credential(
126
+ self,
127
+ *,
128
+ args: dict[str, Any],
129
+ tool_context: ToolContext,
130
+ credential: AuthCredential,
131
+ ) -> Any:
132
+ """Execute the wrapped function with the provided credential.
133
+
134
+ Args:
135
+ args: Arguments for the wrapped function.
136
+ tool_context: The tool context.
137
+ credential: The authentication credential (API key or OAuth2).
138
+
139
+ Returns:
140
+ The result from the wrapped function.
141
+ """
142
+ args_to_call = args.copy()
143
+ signature = inspect.signature(self.func)
144
+
145
+ # Inject the appropriate credential based on type
146
+ if self._into in signature.parameters:
147
+ if isinstance(self._auth_config, ApiKeyAuthConfig):
148
+ args_to_call[self._into] = credential.api_key
149
+ elif isinstance(self._auth_config, OAuth2AuthConfig):
150
+ args_to_call[self._into] = credential.oauth2.access_token
151
+ elif isinstance(self._auth_config, WorkloadAuthConfig):
152
+ args_to_call[self._into] = credential.oauth2.access_token
153
+ else:
154
+ raise ValueError(
155
+ f"Unsupported auth config type: {type(self._auth_config)}"
156
+ )
157
+
158
+ return await super().run_async(args=args_to_call, tool_context=tool_context)