Python: Surface oauth_consent_request events from Responses API in Foundry clients#5070
Python: Surface oauth_consent_request events from Responses API in Foundry clients#5070giles17 wants to merge 6 commits intomicrosoft:mainfrom
oauth_consent_request events from Responses API in Foundry clients#5070Conversation
…soft#5054) Override _parse_chunk_from_openai in both RawFoundryChatClient and RawFoundryAgentChatClient to intercept response.output_item.added events with item.type == 'oauth_consent_request'. The consent link is validated (HTTPS required) and converted to Content.from_oauth_consent_request, which the AG-UI layer already knows how to emit as a CUSTOM event. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 91%
✓ Correctness
The PR correctly adds oauth_consent_request handling to both RawFoundryChatClient and RawFoundryAgentChatClient by overiding _parse_chunk_from_openai, matching the agreed-upon approach from prior discussion. The implementation correctly mirrors the existing pattern, properly validates HTTPS consent links, and delegates non-matching events to super(). Tests cover the happy path, non-HTTPS rejection, and missing consent_link cases for both clients. No correctness issues found.
✓ Security Reliability
The implementation correctly adds oauth_consent_request handling to both Foundry clients by overiding _parse_chunk_from_openai, mirroring the existing AzureAIClient pattern. Security-wise, the HTTPS validation on consent_link is properly implemented, safe attribute access via getattr is used for untrusted event properties, and invalid/missing consent links are logged and rejected. The code correctly falls through to the parent class for all non-OAuth events. Tests cover the happy path, non-HTTPS rejection, and missing consent_link cases. No security or reliability issues found.
✗ Test Coverage
Tests cover both
RawFoundryChatClientandRawFoundryAgentChatClientwith three cases each: valid HTTPS consent link, non-HTTPS rejection, and missing consent_link. This covers the core happy path and two important edge cases. However, there is a notable gap: no test verifies that non-oauth events delegate tosuper()._parse_chunk_from_openai(), which is the fallback path on line 333 (_agent.py) and line 249 (_chat_client.py). Additionally, the tests only assert oncontentsbut do not verify theroleorraw_representationfields of the returnedChatResponseUpdate, meaning the tests wouldn't catch regressions in those fields. The test for the non-HTTPS case also doesn't verify that theChatResponseUpdateis still returned (with empty contents and role='assistant'), only that no consent content is present — this is acceptable but could be stronger. The empty-string consent_link edge case (as distinct from None) is also not tested, though the code handles it via theor "fallback. Overall the coverage is solid for the primary scenarios but mises the super-delegation path.
✓ Design Approach
The approach is correct — overriding
_parse_chunk_from_openaiin both Foundry clients keeps the Azure-specificoauth_consent_requestevent handling out of the base OpenAI class while surfacing it for all Foundry users. The only design concern is that the 30-line implementation block is copy-pasted verbatim into bothRawFoundryChatClientandRawFoundryAgentChatClient. Since neither class inherits from the other, any future change (e.g., addinghttp://localhostas a permitted origin in tests, or changing the warning message) must be made in two places. Extracting a shared module-level helper_parse_oauth_consent_event(event) -> ChatResponseUpdate | Nonewould eliminate this fragility with minimal overhead.
Flagged Issues
- No test verifies that non-oauth events (e.g., event.type != 'response.output_item.added' or item.type != 'oauth_consent_request') correctly delegate to super()._parse_chunk_from_openai(). This is the primary behavioral contract of the override — that all other events pass through unchanged — and is untested in both test files.
Suggestions
- Extract the duplicated ~30-line OAuth consent parsing block into a shared helper (e.g., _try_parse_oauth_consent_event(event) -> ChatResponseUpdate | None) so both RawFoundryChatClient and RawFoundryAgentChatClient call the helper instead of repeating identical logic. This reduces maintenance burden and the risk of the two copies drifting apart on future security-relevant updates.
- Add assertions on update.role ('assistant') and update.raw_representation (the mock event) in the happy-path tests to fully validate the returned ChatResponseUpdate, not just its contents.
- Consider adding a test for consent_link = '' (empty string) to verify it behaves the same as None — the code treats both the same via or "" but this isn't explicitly tested.
- The tests for RawFoundryAgentChatClient use a plain MagicMock for project_client while the RawFoundryChatClient tests use _make_mock_openai_client(). Aligning the mock patterns would improve consistency, though it's harmless since _parse_chunk_from_openai doesn't touch the client.
Automated review by giles17's agents
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Surfaces oauth_consent_request streaming events from the OpenAI Responses API in the Foundry Python clients so downstream callers (e.g., AG-UI) can present OAuth consent links instead of silently dropping them (fixes #5054).
Changes:
- Added Foundry-specific
_parse_chunk_from_openaioverrides to translateoauth_consent_requestoutput items intoContent.from_oauth_consent_request(...). - Added unit tests for both
RawFoundryChatClientandRawFoundryAgentChatClientcovering happy-path, missing link, and non-HTTPS rejection.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| python/packages/foundry/agent_framework_foundry/_chat_client.py | Intercepts response.output_item.added oauth consent items and emits oauth_consent_request content. |
| python/packages/foundry/agent_framework_foundry/_agent.py | Same interception logic for the Foundry agent chat client path. |
| python/packages/foundry/tests/foundry/test_foundry_chat_client.py | Adds unit coverage for oauth consent parsing behavior in RawFoundryChatClient. |
| python/packages/foundry/tests/foundry/test_foundry_agent.py | Adds unit coverage for oauth consent parsing behavior in RawFoundryAgentChatClient. |
python/packages/foundry/agent_framework_foundry/_chat_client.py
Outdated
Show resolved
Hide resolved
python/packages/foundry/agent_framework_foundry/_chat_client.py
Outdated
Show resolved
Hide resolved
python/packages/foundry/agent_framework_foundry/_chat_client.py
Outdated
Show resolved
Hide resolved
- Extract shared helper (try_parse_oauth_consent_event) to avoid duplicated logic between RawFoundryChatClient and RawFoundryAgentChatClient - Use urllib.parse.urlparse() for HTTPS validation instead of case-sensitive startswith check - Sanitize log messages to avoid leaking consent_link tokens; log only item id - Add model=self.model to ChatResponseUpdate to match parent behavior - Add assertions on role, raw_representation, and model in happy-path tests - Add test for empty-string consent_link - Add test verifying non-oauth events delegate to super() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add support for the top-level response.oauth_consent_requested stream event in addition to the response.output_item.added variant. The service may emit either form; handle both so the consent link is reliably surfaced. Extract _validate_consent_link helper within _oauth_helpers.py to reduce nesting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ent` (Responses API) Does Not Surface `oauth_consent_request` as a CUSTOM AG-UI Event
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 94%
✓ Correctness
This diff contains only cosmetic changes: import reordering to follow isort conventions (stdlib → third-party → local with proper blank-line grouping), removal of the unused
Contentimport from_agent.py, and removal of extra blank lines in test files. The substantive feature code (the_parse_chunk_from_openaioverides and_oauth_helpers.py) already exists in the repo. All changes are correct and introduce no functional impact.
✓ Security Reliability
The PR adds OAuth consent event handling to both
RawFoundryChatClientandRawFoundryAgentChatClientby overiding_parse_chunk_from_openaiand delegating to a shared_oauth_helpers.pymodule. From a security/reliability perspective, the implementation is solid: consent links are validated to be HTTPS-only with a valid netloc viaurlparse, attribute access on untrusted event objects usesgetattrwith safe defaults, and the code correctly delegates unrecognized events tosuper(). Test coverage is comprehensive, covering valid HTTPS links, non-HTTPS rejection, missing/empty consent links, super delegation, and the alternateresponse.oauth_consent_requestedevent type. No injection risks, resource leaks, or unhandled failure modes were identified.
✓ Test Coverage
This diff contains only cosmetic changes (import reordering per isort conventions and removing extra blank lines) on top of the functional OAuth consent handling added in earlier commits. The functional changes across the full PR are well-tested: both
RawFoundryChatClientandRawFoundryAgentChatClienthave parallel test suites covering valid HTTPS consent links, non-HTTPS rejection, missing/None consent links, empty-string consent links, delegation to super for non-OAuth events, and the top-levelresponse.oauth_consent_requestedevent type. The shared_oauth_helpers.pymodule lacks its own dedicated unit tests but is thoroughly exercised through the client tests. One minor gap: there are no tests verifying that warning log messages are emitted for rejected or missing consent links, and no test for the edge case of an HTTPS URL with an empty netloc (e.g.,https:///path). These are non-blocking suggestions.
✓ Design Approach
This diff contains only import reordering (placing azure.* third-party imports before the local agent_framework_foundry import, consistent with PEP 8 third-party → first-party ordering) and removal of two stray blank lines in test files. The substantive implementation — _oauth_helpers.py, _parse_chunk_from_openai overrides in both RawFoundryChatClient and RawFoundryAgentChatClient, and accompanying tests — is already present in the repo but not shown in this diff. There are no design, correctness, or abstraction issues in the changed lines.
Suggestions
- Consider using
getattr(event, 'type', None)intry_parse_oauth_consent_event(line 40 of_oauth_helpers.py) instead of bareevent.typeto be defensive against malformed events, though this is consistent with how the parentRawOpenAIChatClient._parse_chunk_from_openaiaccesses the attribute so it's not a regression. - Consider adding a dedicated test file for
_oauth_helpers.py(e.g.,test_oauth_helpers.py) with unit tests fortry_parse_oauth_consent_eventand_validate_consent_linkdirectly. This would make the helper's contract explicit and easier to maintain, and could cover edge cases not yet tested: an HTTPS URL with an empty netloc (e.g.,https:///path), and verification that warning log messages are emitted when consent links are rejected (viacaplogor mocking the logger).
Automated review by giles17's agents
…microsoft#5054) - Use getattr(event, 'type', None) in try_parse_oauth_consent_event for defensive access against malformed events without a type attribute - Add test_oauth_helpers.py with unit tests for _validate_consent_link and try_parse_oauth_consent_event covering edge cases: - HTTPS URL with empty netloc (https:///path) - Warning log messages for rejected consent links - Event objects missing 'type' attribute Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ent` (Responses API) Does Not Surface `oauth_consent_request` as a CUSTOM AG-UI Event
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 97% | Result: All clear
Reviewed: Correctness, Security Reliability, Test Coverage, Design Approach
Automated review by giles17's agents
Motivation and Context
When a Foundry agent uses an MCP server requiring OAuth consent, the
oauth_consent_requeststream event from the Responses API was silently dropped by thecase _:catchall in the base event handler. This meant the consent link was never surfaced to calers, breaking any agent migrated fromAzureAIClienttoFoundryAgent.Fixes #5054
Description
Both
RawFoundryChatClientandRawFoundryAgentChatClientnow override_parse_chunk_from_openaito interceptresponse.output_item.addedevents whereitem.type == "oauth_consent_request". When a valid HTTPSconsent_linkis present, the override emits aContent.from_oauth_consent_request(...)item in theChatResponseUpdate, consistent with how the Assistants API path surfaces the same signal. Non-HTTPS links and missing consent links are logged and rejected. Tests cover the happy path, missing link, and non-HTTPS rejection for both Foundry client classes.Contribution Checklist