Skip to content

Python: Surface oauth_consent_request events from Responses API in Foundry clients#5070

Draft
giles17 wants to merge 6 commits intomicrosoft:mainfrom
giles17:agent/fix-5054-1
Draft

Python: Surface oauth_consent_request events from Responses API in Foundry clients#5070
giles17 wants to merge 6 commits intomicrosoft:mainfrom
giles17:agent/fix-5054-1

Conversation

@giles17
Copy link
Copy Markdown
Contributor

@giles17 giles17 commented Apr 2, 2026

Motivation and Context

When a Foundry agent uses an MCP server requiring OAuth consent, the oauth_consent_request stream event from the Responses API was silently dropped by the case _: catchall in the base event handler. This meant the consent link was never surfaced to calers, breaking any agent migrated from AzureAIClient to FoundryAgent.

Fixes #5054

Description

Both RawFoundryChatClient and RawFoundryAgentChatClient now override _parse_chunk_from_openai to intercept response.output_item.added events where item.type == "oauth_consent_request". When a valid HTTPS consent_link is present, the override emits a Content.from_oauth_consent_request(...) item in the ChatResponseUpdate, 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

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Note: PR autogenerated by giles17's agent

…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 giles17 self-assigned this Apr 2, 2026
Copilot AI review requested due to automatic review settings April 2, 2026 20:28
Copy link
Copy Markdown
Contributor Author

@giles17 giles17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 RawFoundryChatClient and RawFoundryAgentChatClient with 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 to super()._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 on contents but do not verify the role or raw_representation fields of the returned ChatResponseUpdate, meaning the tests wouldn't catch regressions in those fields. The test for the non-HTTPS case also doesn't verify that the ChatResponseUpdate is 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 the or " 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_openai in both Foundry clients keeps the Azure-specific oauth_consent_request event 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 both RawFoundryChatClient and RawFoundryAgentChatClient. Since neither class inherits from the other, any future change (e.g., adding http://localhost as 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 | None would 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

@markwallace-microsoft
Copy link
Copy Markdown
Member

markwallace-microsoft commented Apr 2, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/foundry/agent_framework_foundry
   _agent.py1453476%184–185, 189–191, 196–199, 294, 329–330, 342–344, 346–347, 349–355, 357–358, 360, 362, 366–367, 554–555, 558, 600
   _chat_client.py1431986%83, 85–87, 91–92, 96, 189, 220, 311, 372, 374, 462, 466–467, 469–472
   _oauth_helpers.py310100% 
TOTAL27102318888% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
5397 20 💤 0 ❌ 0 🔥 1m 31s ⏱️

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_openai overrides to translate oauth_consent_request output items into Content.from_oauth_consent_request(...).
  • Added unit tests for both RawFoundryChatClient and RawFoundryAgentChatClient covering 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.

Copilot and others added 3 commits April 2, 2026 20:38
- 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
Copy link
Copy Markdown
Contributor Author

@giles17 giles17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Content import from _agent.py, and removal of extra blank lines in test files. The substantive feature code (the _parse_chunk_from_openai overides 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 RawFoundryChatClient and RawFoundryAgentChatClient by overiding _parse_chunk_from_openai and delegating to a shared _oauth_helpers.py module. From a security/reliability perspective, the implementation is solid: consent links are validated to be HTTPS-only with a valid netloc via urlparse, attribute access on untrusted event objects uses getattr with safe defaults, and the code correctly delegates unrecognized events to super(). Test coverage is comprehensive, covering valid HTTPS links, non-HTTPS rejection, missing/empty consent links, super delegation, and the alternate response.oauth_consent_requested event 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 RawFoundryChatClient and RawFoundryAgentChatClient have 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-level response.oauth_consent_requested event type. The shared _oauth_helpers.py module 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) in try_parse_oauth_consent_event (line 40 of _oauth_helpers.py) instead of bare event.type to be defensive against malformed events, though this is consistent with how the parent RawOpenAIChatClient._parse_chunk_from_openai accesses 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 for try_parse_oauth_consent_event and _validate_consent_link directly. 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 (via caplog or mocking the logger).

Automated review by giles17's agents

Copilot and others added 2 commits April 2, 2026 20:51
…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
Copy link
Copy Markdown
Contributor Author

@giles17 giles17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 97% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage, Design Approach


Automated review by giles17's agents

@giles17 giles17 marked this pull request as draft April 2, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: FoundryAgent (Responses API) Does Not Surface oauth_consent_request as a CUSTOM AG-UI Event

3 participants