-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Description
When a Foundry-hosted agent is wrapped with Agent.as_tool() and the underlying agent requires OAuth consent for a connected MCP tool, the consent event is silently consumed inside the as_tool() implementation. The caller receives either an empty result or a partial text result with no indication that OAuth consent is required.
There is no supported way to intercept or observe consent events emitted by the sub-agent when calling it as a tool.
Background
Issue #4197 fixed the underlying SDK and AG-UI layers so that CUSTOM(oauth_consent_request) is now correctly emitted from the sub-agent's event stream when a Foundry-hosted agent requires OAuth consent for a connected MCP tool. However, as_tool() consumes that stream internally and still discards the event before it reaches the caller. #4197 is a prerequisite for this bug, not a resolution of it.
Root Cause
Looking at _agents.py, as_tool() has two internal execution paths. Both discard consent events:
Path 1 - no stream_callback (the common case)
# _agents.py - agent_wrapper() inside as_tool()
if stream_callback is None:
# Use non-streaming mode
return (await self.run(input_text, stream=False, **forwarded_kwargs)).textNon-streaming mode. Returns .text only. The agent run completes entirely internally - there is no way for a consent event to surface to the caller.
Path 2 - with stream_callback
# _agents.py - agent_wrapper() inside as_tool()
async for update in self.run(input_text, stream=True, **forwarded_kwargs):
response_updates.append(update)
if is_async_callback:
await stream_callback(update)
else:
stream_callback(update)
# Create final text from accumulated updates
return AgentResponse.from_updates(response_updates).textThe stream_callback receives AgentResponseUpdate objects during the run. However:
- The return value is still
.textonly - the caller gets no structured signal that consent is required. stream_callbackis designed for UI streaming feedback, not structured event interception. It is not a viable workaround for this use case.- Even if a consent event were observable via the callback, there is no mechanism to communicate it back to the caller as a return value or exception.
The result in both cases: there is no code path in as_tool() where a consent event can cause anything other than an empty string return.
Expected Behaviour
One of:
-
Option A (minimal): When a consent event is emitted during the sub-agent run,
as_tool()raises a typed exception that the caller can catch:class ConsentRequiredException(Exception): def __init__(self, url: str): self.url = url
This lets callers handle it cleanly without reimplementing the run loop:
try: result = await tool.ainvoke(...) except ConsentRequiredException as e: return f"__oauth_consent_required|{e.url}"
-
Option B (preferred):
as_tool()accepts an async generator hook oron_eventcallback that exposes raw events - not justAgentResponseUpdate- so the caller can observe consent events and other structured events without reimplementingagent.run()internally.
Actual Behaviour
as_tool() runs the sub-agent internally and only returns the final .text value. Any consent events emitted during the run are discarded. The caller has no way to detect them.
Workaround
I temporarily worked around this by bypassing as_tool() entirely and driving agent.run() manually inside our own tool wrapper:
async for event in agent.run(messages=[...], stream=True, session=tool_session):
event_type = _norm_type(getattr(event, "type", None))
# manually detect CUSTOM(oauth_consent_request)
# manually accumulate text output
# manually handle errorsThis means we have reimplemented the internals of as_tool() - including text extraction across multiple possible event shapes - and we are now tightly coupled to internal event structure that could change without notice. This is fragile and hard to maintain.
Code Sample
Error Messages / Stack Traces
Package Versions
agent-framework-core==1.0.0rc3, agent-framework-azure-ai==1.0.0rc3
Python Version
Python 3.12
Additional Context
This is somewhat related to #4213. Both issues involve agent runtime events that are not surfaced to consumers, but they occur at different layers and have independent fixes. #4213 is a gap in the AG-UI SSE emission layer (_emit_content); this issue is a gap in the agent orchestration layer (as_tool()).