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.
- veadk/__init__.py +37 -0
- veadk/a2a/__init__.py +13 -0
- veadk/a2a/agent_card.py +45 -0
- veadk/a2a/remote_ve_agent.py +390 -0
- veadk/a2a/utils/__init__.py +13 -0
- veadk/a2a/utils/agent_to_a2a.py +170 -0
- veadk/a2a/ve_a2a_server.py +93 -0
- veadk/a2a/ve_agent_executor.py +78 -0
- veadk/a2a/ve_middlewares.py +313 -0
- veadk/a2a/ve_task_store.py +37 -0
- veadk/agent.py +402 -0
- veadk/agent_builder.py +93 -0
- veadk/agents/loop_agent.py +68 -0
- veadk/agents/parallel_agent.py +72 -0
- veadk/agents/sequential_agent.py +64 -0
- veadk/auth/__init__.py +13 -0
- veadk/auth/base_auth.py +22 -0
- veadk/auth/ve_credential_service.py +203 -0
- veadk/auth/veauth/__init__.py +13 -0
- veadk/auth/veauth/apmplus_veauth.py +58 -0
- veadk/auth/veauth/ark_veauth.py +75 -0
- veadk/auth/veauth/base_veauth.py +50 -0
- veadk/auth/veauth/cozeloop_veauth.py +13 -0
- veadk/auth/veauth/opensearch_veauth.py +75 -0
- veadk/auth/veauth/postgresql_veauth.py +75 -0
- veadk/auth/veauth/prompt_pilot_veauth.py +60 -0
- veadk/auth/veauth/speech_veauth.py +54 -0
- veadk/auth/veauth/utils.py +69 -0
- veadk/auth/veauth/vesearch_veauth.py +62 -0
- veadk/auth/veauth/viking_mem0_veauth.py +91 -0
- veadk/cli/__init__.py +13 -0
- veadk/cli/cli.py +58 -0
- veadk/cli/cli_clean.py +87 -0
- veadk/cli/cli_create.py +163 -0
- veadk/cli/cli_deploy.py +233 -0
- veadk/cli/cli_eval.py +215 -0
- veadk/cli/cli_init.py +214 -0
- veadk/cli/cli_kb.py +110 -0
- veadk/cli/cli_pipeline.py +285 -0
- veadk/cli/cli_prompt.py +86 -0
- veadk/cli/cli_update.py +106 -0
- veadk/cli/cli_uploadevalset.py +139 -0
- veadk/cli/cli_web.py +143 -0
- veadk/cloud/__init__.py +13 -0
- veadk/cloud/cloud_agent_engine.py +485 -0
- veadk/cloud/cloud_app.py +475 -0
- veadk/config.py +115 -0
- veadk/configs/__init__.py +13 -0
- veadk/configs/auth_configs.py +133 -0
- veadk/configs/database_configs.py +132 -0
- veadk/configs/model_configs.py +78 -0
- veadk/configs/tool_configs.py +54 -0
- veadk/configs/tracing_configs.py +110 -0
- veadk/consts.py +74 -0
- veadk/evaluation/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/adk_evaluator.py +302 -0
- veadk/evaluation/base_evaluator.py +642 -0
- veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
- veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +339 -0
- veadk/evaluation/eval_set_file_loader.py +48 -0
- veadk/evaluation/eval_set_recorder.py +146 -0
- veadk/evaluation/types.py +65 -0
- veadk/evaluation/utils/prometheus.py +196 -0
- veadk/integrations/__init__.py +13 -0
- veadk/integrations/ve_apig/__init__.py +13 -0
- veadk/integrations/ve_apig/ve_apig.py +349 -0
- veadk/integrations/ve_apig/ve_apig_utils.py +332 -0
- veadk/integrations/ve_code_pipeline/__init__.py +13 -0
- veadk/integrations/ve_code_pipeline/ve_code_pipeline.py +431 -0
- veadk/integrations/ve_cozeloop/__init__.py +13 -0
- veadk/integrations/ve_cozeloop/ve_cozeloop.py +96 -0
- veadk/integrations/ve_cr/__init__.py +13 -0
- veadk/integrations/ve_cr/ve_cr.py +220 -0
- veadk/integrations/ve_faas/__init__.py +13 -0
- veadk/integrations/ve_faas/template/cookiecutter.json +15 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/config.yaml.example +6 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/deploy.py +106 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/agent.py +25 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +202 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/requirements.txt +3 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +49 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/__init__.py +14 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/agent.py +27 -0
- veadk/integrations/ve_faas/ve_faas.py +754 -0
- veadk/integrations/ve_faas/ve_faas_utils.py +408 -0
- veadk/integrations/ve_faas/web_template/cookiecutter.json +20 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example +2 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py +44 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py +123 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py +46 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py +36 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt +4 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css +368 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js +0 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html +24 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html +53 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html +45 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html +29 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html +14 -0
- veadk/integrations/ve_identity/__init__.py +110 -0
- veadk/integrations/ve_identity/auth_config.py +261 -0
- veadk/integrations/ve_identity/auth_mixins.py +650 -0
- veadk/integrations/ve_identity/auth_processor.py +385 -0
- veadk/integrations/ve_identity/function_tool.py +158 -0
- veadk/integrations/ve_identity/identity_client.py +864 -0
- veadk/integrations/ve_identity/mcp_tool.py +181 -0
- veadk/integrations/ve_identity/mcp_toolset.py +431 -0
- veadk/integrations/ve_identity/models.py +228 -0
- veadk/integrations/ve_identity/token_manager.py +188 -0
- veadk/integrations/ve_identity/utils.py +151 -0
- veadk/integrations/ve_prompt_pilot/__init__.py +13 -0
- veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +85 -0
- veadk/integrations/ve_tls/__init__.py +13 -0
- veadk/integrations/ve_tls/utils.py +116 -0
- veadk/integrations/ve_tls/ve_tls.py +212 -0
- veadk/integrations/ve_tos/ve_tos.py +710 -0
- veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
- veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +308 -0
- veadk/knowledgebase/__init__.py +17 -0
- veadk/knowledgebase/backends/__init__.py +13 -0
- veadk/knowledgebase/backends/base_backend.py +72 -0
- veadk/knowledgebase/backends/in_memory_backend.py +91 -0
- veadk/knowledgebase/backends/opensearch_backend.py +162 -0
- veadk/knowledgebase/backends/redis_backend.py +172 -0
- veadk/knowledgebase/backends/utils.py +92 -0
- veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +608 -0
- veadk/knowledgebase/entry.py +25 -0
- veadk/knowledgebase/knowledgebase.py +307 -0
- veadk/memory/__init__.py +35 -0
- veadk/memory/long_term_memory.py +365 -0
- veadk/memory/long_term_memory_backends/__init__.py +13 -0
- veadk/memory/long_term_memory_backends/base_backend.py +35 -0
- veadk/memory/long_term_memory_backends/in_memory_backend.py +67 -0
- veadk/memory/long_term_memory_backends/mem0_backend.py +155 -0
- veadk/memory/long_term_memory_backends/opensearch_backend.py +124 -0
- veadk/memory/long_term_memory_backends/redis_backend.py +140 -0
- veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +189 -0
- veadk/memory/short_term_memory.py +252 -0
- veadk/memory/short_term_memory_backends/__init__.py +13 -0
- veadk/memory/short_term_memory_backends/base_backend.py +31 -0
- veadk/memory/short_term_memory_backends/mysql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/postgresql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/sqlite_backend.py +55 -0
- veadk/memory/short_term_memory_processor.py +100 -0
- veadk/processors/__init__.py +26 -0
- veadk/processors/base_run_processor.py +120 -0
- veadk/prompts/__init__.py +13 -0
- veadk/prompts/agent_default_prompt.py +30 -0
- veadk/prompts/prompt_evaluator.py +20 -0
- veadk/prompts/prompt_memory_processor.py +55 -0
- veadk/prompts/prompt_optimization.py +150 -0
- veadk/runner.py +732 -0
- veadk/tools/__init__.py +13 -0
- veadk/tools/builtin_tools/__init__.py +13 -0
- veadk/tools/builtin_tools/agent_authorization.py +94 -0
- veadk/tools/builtin_tools/generate_image.py +23 -0
- veadk/tools/builtin_tools/image_edit.py +300 -0
- veadk/tools/builtin_tools/image_generate.py +446 -0
- veadk/tools/builtin_tools/lark.py +67 -0
- veadk/tools/builtin_tools/las.py +24 -0
- veadk/tools/builtin_tools/link_reader.py +66 -0
- veadk/tools/builtin_tools/llm_shield.py +381 -0
- veadk/tools/builtin_tools/load_knowledgebase.py +97 -0
- veadk/tools/builtin_tools/mcp_router.py +29 -0
- veadk/tools/builtin_tools/run_code.py +113 -0
- veadk/tools/builtin_tools/tts.py +253 -0
- veadk/tools/builtin_tools/vesearch.py +49 -0
- veadk/tools/builtin_tools/video_generate.py +363 -0
- veadk/tools/builtin_tools/web_scraper.py +76 -0
- veadk/tools/builtin_tools/web_search.py +83 -0
- veadk/tools/demo_tools.py +58 -0
- veadk/tools/load_knowledgebase_tool.py +149 -0
- veadk/tools/sandbox/__init__.py +13 -0
- veadk/tools/sandbox/browser_sandbox.py +37 -0
- veadk/tools/sandbox/code_sandbox.py +40 -0
- veadk/tools/sandbox/computer_sandbox.py +34 -0
- veadk/tracing/__init__.py +13 -0
- veadk/tracing/base_tracer.py +58 -0
- veadk/tracing/telemetry/__init__.py +13 -0
- veadk/tracing/telemetry/attributes/attributes.py +29 -0
- veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +180 -0
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +858 -0
- veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +152 -0
- veadk/tracing/telemetry/attributes/extractors/types.py +164 -0
- veadk/tracing/telemetry/exporters/__init__.py +13 -0
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +558 -0
- veadk/tracing/telemetry/exporters/base_exporter.py +39 -0
- veadk/tracing/telemetry/exporters/cozeloop_exporter.py +129 -0
- veadk/tracing/telemetry/exporters/inmemory_exporter.py +248 -0
- veadk/tracing/telemetry/exporters/tls_exporter.py +139 -0
- veadk/tracing/telemetry/opentelemetry_tracer.py +320 -0
- veadk/tracing/telemetry/telemetry.py +411 -0
- veadk/types.py +47 -0
- veadk/utils/__init__.py +13 -0
- veadk/utils/audio_manager.py +95 -0
- veadk/utils/auth.py +294 -0
- veadk/utils/logger.py +59 -0
- veadk/utils/mcp_utils.py +44 -0
- veadk/utils/misc.py +184 -0
- veadk/utils/patches.py +101 -0
- veadk/utils/volcengine_sign.py +205 -0
- veadk/version.py +15 -0
- veadk_python-0.2.27.dist-info/METADATA +373 -0
- veadk_python-0.2.27.dist-info/RECORD +218 -0
- veadk_python-0.2.27.dist-info/WHEEL +5 -0
- veadk_python-0.2.27.dist-info/entry_points.txt +2 -0
- veadk_python-0.2.27.dist-info/licenses/LICENSE +201 -0
- 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)
|