From 7b7f94a79dd2352c1c43fb972da05bb386017e6b Mon Sep 17 00:00:00 2001 From: Shalabh Gupta Date: Tue, 10 Mar 2026 13:13:16 +0530 Subject: [PATCH] Fix OAuth consent event forwarding in as_tool() Fixes #4499 Adds exception-based OAuth consent event forwarding to enable proper OAuth flows when using agents as tools. Changes: - Added OAuthConsentRequiredException to exceptions.py - Modified as_tool() to detect oauth_consent_request events in both streaming and non-streaming modes - Raises OAuthConsentRequiredException with consent_url when OAuth consent is required - Parent agents can now catch this exception and forward the consent request to the user This implements the exception-based approach (Option A) discussed in issue #4499, allowing proper OAuth flow handling when wrapping agents as tools. The fix ensures that OAuth consent requests are not silently swallowed when using as_tool(), enabling proper multi-agent OAuth workflows. --- .../packages/core/agent_framework/_agents.py | 18 ++++++++- .../core/agent_framework/exceptions.py | 40 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index 3aaf9f1419..3276698d09 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -51,7 +51,7 @@ map_chat_to_agent_update, normalize_messages, ) -from .exceptions import AgentInvalidResponseException +from .exceptions import AgentInvalidResponseException, OAuthConsentRequiredException from .observability import AgentTelemetryLayer if sys.version_info >= (3, 13): @@ -532,11 +532,25 @@ async def agent_wrapper(**kwargs: Any) -> str: if stream_callback is None: # Use non-streaming mode - return (await self.run(input_text, stream=False, session=parent_session, **forwarded_kwargs)).text + response = await self.run(input_text, stream=False, session=parent_session, **forwarded_kwargs) + # Check for OAuth consent request in response + for content in response.contents: + if content.type == 'oauth_consent_request': + consent_url = content.get('consent_link') or content.get('url') or '' + from .exceptions import OAuthConsentRequiredException + raise OAuthConsentRequiredException(consent_url) + return response.text # Use streaming mode - accumulate updates and create final response response_updates: list[AgentResponseUpdate] = [] async for update in self.run(input_text, stream=True, session=parent_session, **forwarded_kwargs): + # Check for OAuth consent request in update + for content in update.contents: + if content.type == 'oauth_consent_request': + consent_url = content.get('consent_link') or content.get('url') or '' + from .exceptions import OAuthConsentRequiredException + raise OAuthConsentRequiredException(consent_url) + response_updates.append(update) if is_async_callback: await stream_callback(update) # type: ignore[misc] diff --git a/python/packages/core/agent_framework/exceptions.py b/python/packages/core/agent_framework/exceptions.py index f38aa38590..7560da3ca5 100644 --- a/python/packages/core/agent_framework/exceptions.py +++ b/python/packages/core/agent_framework/exceptions.py @@ -204,6 +204,46 @@ class SettingNotFoundError(AgentFrameworkException): # endregion + + +class OAuthConsentRequiredException(AgentException): + """Raised when a sub-agent tool requires OAuth consent. + + This exception is raised by as_tool() when a wrapped agent returns + an oauth_consent_request event. The parent agent should catch this + exception and forward the consent request to the user. + + Attributes: + consent_url: The OAuth consent URL that the user must visit. + """ + + def __init__(self, consent_url: str): + self.consent_url = consent_url + super().__init__( + f'OAuth consent required. Please visit: {consent_url}', + log_level=logging.INFO, + ) + + + +class OAuthConsentRequiredException(AgentException): + """Raised when a sub-agent tool requires OAuth consent. + + This exception is raised by as_tool() when a wrapped agent returns + an oauth_consent_request event. The parent agent should catch this + exception and forward the consent request to the user. + + Attributes: + consent_url: The OAuth consent URL that the user must visit. + """ + + def __init__(self, consent_url: str): + self.consent_url = consent_url + super().__init__( + f'OAuth consent required. Please visit: {consent_url}', + log_level=logging.INFO, + ) + # region Workflow Exceptions