feat: add azure-ai-agentserver-activity package (Activity Protocol)#47489
feat: add azure-ai-agentserver-activity package (Activity Protocol)#47489Hameedkunkanoor wants to merge 39 commits into
Conversation
Initial release of the Activity Protocol host package for Azure AI Foundry hosted agents. Provides Starlette-based HTTP endpoints for Activity Protocol traffic with M365 Agents SDK integration. Package (azure-ai-agentserver-activity 1.0.0b1): - ActivityAgentServerHost with POST /activity/messages and /api/messages - Decorator API: @app.activity(type), @app.error for zero-config usage - Custom handler support for full M365 SDK control - Auto-initialization of M365 SDK from environment variables - MSAL auth patches for Foundry container MAIB auth (apply_msal_patches) - Session ID resolution (query param, header, config, UUID fallback) - Activity/session ID sanitization for header injection defense - OpenTelemetry distributed tracing and W3C Baggage propagation - Error-source classification (x-platform-error-source) Samples: - simple_activity_agent (echo bot) - streaming_activity_agent (Azure OpenAI streaming) - cards_activity_agent (Adaptive/Hero/Thumbnail/Receipt cards) - auto_signin_activity_agent (OAuth with Graph/GitHub) - semantic_kernel_activity_agent (SK agent with tools, multi-turn) - suggested_actions_activity_agent (quick-reply buttons) Tests: 26 tests covering routes, session resolution, error classification, tracing, decorator pattern, and ID sanitization edge cases.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new preview package, azure-ai-agentserver-activity (Activity Protocol host), adding Starlette endpoints intended for Azure AI Foundry hosted agents and integrating with the Microsoft 365 Agents SDK (decorator mode + custom-handler mode). It also wires the new package into the agentserver CI/test targeting so it is built and validated with the rest of the service directory.
Changes:
- Added
azure-ai-agentserver-activitypackage withActivityAgentServerHost, M365 bridge + MSAL patches, ID sanitization, tracing/baggage, and error-source classification. - Added unit tests and multiple runnable samples (simple, streaming, cards, suggested actions, semantic-kernel, auto sign-in).
- Updated
sdk/agentserver/ci.ymlandsdk/agentserver/tests.ymlto include the new package in artifact/test targeting.
Reviewed changes
Copilot reviewed 39 out of 40 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/agentserver/tests.yml | Adds activity package to test targeting string. |
| sdk/agentserver/ci.yml | Adds activity package to pipeline artifacts. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/test_tracing.py | Adds tracing + baggage propagation tests. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/test_session_headers.py | Adds session resolution/response header tests. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/test_server_routes.py | Adds route/endpoint behavior tests. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/test_sanitize_id.py | Adds _sanitize_id edge-case tests. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/test_id_sanitization.py | Adds end-to-end activity-id sanitization tests. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/test_error_source_classification.py | Adds tests for x-platform-error-source classification. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/test_decorator_pattern.py | Adds decorator wiring tests for bridge handler. |
| sdk/agentserver/azure-ai-agentserver-activity/tests/conftest.py | Adds shared fixtures + marker declaration. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/README.md | Documents available samples and usage patterns. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/simple_activity_agent/simple_activity_agent.py | Simple decorator-based echo agent sample. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/simple_activity_agent/requirements.txt | Sample dependencies. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/streaming_activity_agent/streaming_activity_agent.py | Streaming Azure OpenAI sample using streaming_response. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/streaming_activity_agent/requirements.txt | Sample dependencies. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/cards_activity_agent/cards_activity_agent.py | Rich cards sample (adaptive/hero/thumbnail/receipt). |
| sdk/agentserver/azure-ai-agentserver-activity/samples/cards_activity_agent/requirements.txt | Sample dependencies. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/suggested_actions_activity_agent/suggested_actions_activity_agent.py | Suggested actions / quick-reply buttons sample. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/suggested_actions_activity_agent/requirements.txt | Sample dependencies. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/semantic_kernel_activity_agent/semantic_kernel_activity_agent.py | Semantic Kernel multi-turn + streaming sample. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/semantic_kernel_activity_agent/requirements.txt | Sample dependencies. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/auto_signin_activity_agent/auto_signin_activity_agent.py | OAuth auto sign-in sample using handler pattern + MSAL patches. |
| sdk/agentserver/azure-ai-agentserver-activity/samples/auto_signin_activity_agent/requirements.txt | Sample dependencies. |
| sdk/agentserver/azure-ai-agentserver-activity/README.md | Package README describing host usage and API surface. |
| sdk/agentserver/azure-ai-agentserver-activity/pyrightconfig.json | Pyright config excluding samples. |
| sdk/agentserver/azure-ai-agentserver-activity/pyproject.toml | Package metadata, deps, and Azure SDK build config. |
| sdk/agentserver/azure-ai-agentserver-activity/MANIFEST.in | sdist packaging manifest. |
| sdk/agentserver/azure-ai-agentserver-activity/LICENSE | MIT license file. |
| sdk/agentserver/azure-ai-agentserver-activity/dev_requirements.txt | Dev/test requirements. |
| sdk/agentserver/azure-ai-agentserver-activity/cspell.json | Spellchecker configuration. |
| sdk/agentserver/azure-ai-agentserver-activity/CHANGELOG.md | Initial preview release changelog. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/agentserver/activity/py.typed | Marks package as typed. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/agentserver/activity/_version.py | Defines package version. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/agentserver/activity/_m365_bridge.py | Implements M365 SDK lazy-init + bridge handler + MSAL patching. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/agentserver/activity/_constants.py | Defines Activity protocol constants and header names. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/agentserver/activity/_activity.py | Implements ActivityAgentServerHost endpoint, sanitization, tracing, and error handling. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/agentserver/activity/init.py | Exposes public API (ActivityAgentServerHost, apply_msal_patches). |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/agentserver/init.py | Namespace package init. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/ai/init.py | Namespace package init. |
| sdk/agentserver/azure-ai-agentserver-activity/azure/init.py | Namespace package init. |
| request.method, request.url.path, str(request.query_params), | ||
| request.headers.get("content-type", ""), | ||
| ) | ||
| logger.debug("Activity endpoint headers: %s", dict(request.headers)) |
| ) | ||
| logger.debug("Activity endpoint headers: %s", dict(request.headers)) | ||
|
|
||
| inbound_conversation_id = request.headers.get(ActivityConstants.CONVERSATION_ID_HEADER, "") |
| agent_app, adapter = _ensure_m365_initialized() | ||
|
|
||
| # Replay pending decorator registrations onto the real AgentApplication | ||
| if _lazy_agent_app is not None and not _lazy_agent_app._replayed: | ||
| _lazy_agent_app._replay_on(agent_app) |
| import pytest | ||
| from httpx import ASGITransport, AsyncClient | ||
| from starlette.responses import JSONResponse | ||
|
|
||
| from azure.ai.agentserver.activity import ActivityAgentServerHost |
| @@ -0,0 +1,82 @@ | |||
| # Azure AI Agent Server Activity client library for Python | |||
| `ActivityAgentServerHost` is an `AgentServerHost` subclass for Activity Protocol traffic. It provides: | ||
|
|
||
| - `POST /activity/messages` for inbound activities. | ||
|
|
…al pattern - Remove AGENTAPPLICATION fallback defaults from _initialize_default_env_vars (STARTTYPINGTIMER, REMOVERECIPIENTMENTION, NORMALIZEMENTIONS, USERAUTHORIZATION__AUTOSIGNIN) - Derive CONNECTIONS__ vars from Foundry-native env vars (FOUNDRY_AGENT_BLUEPRINT_CLIENT_ID, FOUNDRY_AGENT_TENANT_ID) using helper functions _get_nonempty / _set_if_missing - Replace old archive-style samples (auto_signin, cards, semantic_kernel, simple, streaming, suggested_actions) with minimal invocations-pattern samples: - echo-agent: Tier 1 zero-config, auto-init from env vars - multi-protocol-agent: Tier 2 Activity + Invocations on one host - self-hosted-agent: Tier 3 full M365 SDK control
…ging - Add _BaggageSpanProcessor (subclass of SDK SpanProcessor) to promote all W3C baggage entries onto every child span - Replace single-logger filter with global setLogRecordFactory so SessionId/UserIsolationKey/ChatIsolationKey/Protocol appear on all loggers' records - Resolve correlation ids and bind contextvars at the top of the endpoint so boundary logs are populated - Add structured request/response logging plus activity.request/activity.response/activity.error span events with full values
…cing The Foundry gateway already emits the turn-level request span and core's TraceContextMiddleware + _FoundryEnrichmentSpanProcessor propagate trace context and stamp identity/session/conversation/project onto every child span. The activity package now only sets baggage (its source-of-truth correlation keys) and emits structured request/response/error logs, avoiding a redundant wrapper span and duplicate/conflicting gen_ai.* + microsoft.foundry.project.id attributes. Verified live (groot-v2 v6): no handle_activity spans, child spans still carry all correlation keys + identity, reminder/proactive flow fully traced, zero errors.
- Add aiohttp constraint to core package (>=3.10.0,<4.0.0a1) to prevent pre-release 4.0.0a1 which fails to compile on Python 3.11 due to missing longintrepr.h - Import Token from opentelemetry.context for proper type hinting - Type baggage_token as Optional[Token[Any]] instead of Optional[object] - Add explicit Response type annotation for handler response with type: ignore for JSONResponse variance
- Remove unused JSONResponse import - Fix opentelemetry import grouping (separate from starlette) - Add missing docstring parameters: 'record' to _enrich_record - Add complete docstrings with param/return types for: - _sanitize_id: document value param and return - _classify_error: document exc param and return tuple - activity() decorator: add return type - error() decorator: add return type and fn param type - _response_body_preview: document response, limit, and return - _create_activity_endpoint: document request param and return
… (b4/b1)" This reverts commit bfe978e.
… asserts, yaml stub ignore
…n prerelease build
…r opt-in) ActivityAgentServerHost now defaults to the simple agent outbound-auth model: the agent instance identity (FOUNDRY_AGENT_INSTANCE_CLIENT_ID) mints the Bot Connector token directly via UserManagedIdentity scoped to api.botframework.com/.default, with authenticated Bearer claims (appid=instance id). This is what a single-tenant Teams bot whose msaAppId is the agent instance identity requires for the connector to accept outbound replies. Pass digital_worker=True to keep the prior blueprint + federated-identity (FMI) model unchanged (blueprint client id, agentic scope, anonymous claims, MSAL FMI patch). Adds tests/test_auth_model.py (6 tests) covering both modes, the default, env-override precedence, and the FMI-patch gating.
… minimal test env
…tserver-activity-protocol # Conflicts: # sdk/agentserver/azure-ai-agentserver-invocations/pyproject.toml
…t call ID) Aligns the activity package with PR #47584: reads x-agent-user-id and x-agent-foundry-call-id, binds them to the request-scoped FoundryAgentRequestContext, exposes request.state.user_id/call_id, renames context/log/baggage fields, bumps core dep floor to >=2.0.0b7 and version to 1.0.0b2. Also reverts the invocations aiohttp cap to match origin/main.
Suppress the opentelemetry.context.Token private-import pyright error, and resolve pylint findings in _activity.py, _m365_bridge.py and __init__.py (docstring types/returns, line lengths, grouped imports, and targeted disables for intentional patterns).
…ple type-check (ghcopilot) Re-add aiohttp>=3.10.0,<4.0.0a0 to invocations so the --pre CI install does not try to build the broken aiohttp 4.0.0a1 alpha. Set type_check_samples=false for ghcopilot, whose samples directory contains no .py files (mypy exits 2 with 'no files to check').
…bridge logging, tidy sample docstrings
…helper, doc consistency Redesign construction into factory classmethods (from_request_handler / from_agent_application) so invalid option combinations cannot be expressed. Add public seed_connection_env() static helper and move connection-env seeding into build_m365_app's env-read path, so only the config=None path seeds (callers who bring their own config/connection_manager seed themselves). Trim request.state to the parsed activity dict. Refresh README/CHANGELOG/init docstrings, add mypy.ini, and update tests + self-hosted-agent sample.
…tom-handler, doc parity Renumber the in-package samples as an ordered learning path (01-echo, 02-custom-components, 03-self-hosted-app, 04-custom-handler, 05-multi-protocol). Add the missing 02-custom-components (override one M365 component) and 04-custom-handler (from_request_handler with an outbound Bot Connector reply) samples, and add azure-ai-agentserver-invocations to the multi-protocol requirements. Bring every doc surface (README usage patterns + examples, __init__ module docstring, CHANGELOG) to parity so all four construction variants (default, overrides, injected app, custom handler) are enumerated consistently; normalize CHANGELOG/README to Markdown backticks and document the /api/messages alias.
…tserver-activity-protocol # Conflicts: # sdk/agentserver/azure-ai-agentserver-ghcopilot/pyproject.toml
opentelemetry-sdk 1.43.0 changed span._attributes from a mutable dict to a BoundedAttributes that rejects item assignment, so _FoundryEnrichmentSpanProcessor._on_ending() silently stopped stamping gen_ai.agent.* / microsoft.a365.agent.* attributes onto exported spans (KeyError in test_tracing). Resolve the BoundedAttributes backing dict (._dict) before writing so enrichment works on both otel-sdk <=1.42 and >=1.43. Verified: full core suite 106 passed / 5 skipped under otel-sdk 1.43.0 and 1.42.0.
…soft_agents imports in _process_turn
…cy (1.0.0b4) - Split Starlette turn adapter into _cloud_adapter.py (pure aiohttp-replica); extract config helpers into _config.py. - Adopt M365 CloudAdapter process_request delegation; inline __configure into __init__; extract _build_m365_stack; single-source default storage via _resolve_storage (build_m365_app now requires resolved storage). - Naming consistency: StarletteCloudAdapter, build_bridge_handler, _build_bad_request; unify logger names to module tags (.host / .bridge). - Terminology: connection_config kwarg + property (was config); read from self._connection_config. - Tests: +37 (78->79 incl. host.run() entrypoint regression guard); remove dead activity_client fixture; add test_config_env.py + test_bridge_turn.py. - Bump version 1.0.0b3 -> 1.0.0b4; CHANGELOG entry.
- Run black (line-length 120) across azure/, tests/, samples/ so the CI Black check passes. - Merge adjacent string literals in two log calls to clear implicit-str-concat (W1404) black surfaced; pylint now 10.00/10. - Verified: black --check clean, pylint 10.00, mypy clean, pyright 0 errors, 79 tests passing.
…M365 SDK)
Two CI failures, both from the M365 Agents SDK being an optional dependency that
my local venv happened to have installed:
- Pylint E0401 (_activity.py:489): the inline rom microsoft_agents.hosting.core
import MemoryStorage in _resolve_storage lacked the import-error suppression
every other M365 import in the package uses. Added
# pylint: disable=import-error,no-name-in-module (same pattern as _m365_bridge).
- whl_no_aio test collection: test_bridge_turn.py imported microsoft_agents at
module top level, aborting collection when the SDK is absent. Guarded it with
pytest.importorskip("microsoft_agents.hosting.core").
- Regression from the earlier storage single-sourcing: _build_m365_stack called
_resolve_storage (which imports MemoryStorage) even on the injected-agent_app
path, so ActivityAgentServerHost(agent_app=...) newly required M365. Restored
the guard so storage is only resolved when building; build_m365_app storage is
Optional again with an explicit boundary check (single default still lives in
_resolve_storage).
Reproduced the no-M365 env with a meta-path block: 55 passed, 1 skipped (was 8
failed). With M365: 79 passed, pylint 10.00/10, mypy clean, pyright 0 errors,
black clean.
| # contract we are overriding (the SDK always passes it positionally), but | ||
| # this DefaultAzureCredential/FMI flow derives the tenant from the | ||
| # federated-identity exchange, so we do not read it here. | ||
| del tenant_id |
There was a problem hiding this comment.
is this in error , odd to be able to pass it and then deleted?
There was a problem hiding this comment.
I've pushed a change that drops the del and instead renames the parameter to _tenant_id (leading-underscore convention for an intentionally-unused arg), with a comment explaining why it's unused. Should read cleaner now.
| if client_id: | ||
| credential_kwargs[MsalPatch.MANAGED_IDENTITY_CLIENT_ID_KEY] = client_id | ||
|
|
||
| credential = DefaultAzureCredential(**credential_kwargs) |
There was a problem hiding this comment.
can it be better to ask the user to pass in the credential they want to use? The fallback mechanism for dac could cause for an unwanted mech to be used.
There was a problem hiding this comment.
Switched from DefaultAzureCredential to ManagedIdentityCredential — it supports the FMI identity_config exchange but has no fallback chain, so it can only use the container's managed identity and can't silently pick an unintended mechanism. That removes the risk you flagged. I kept it internal rather than exposing a credential parameter because for the hosted digital-worker path the container MI is the only valid identity. Longer term I've opened a PR on the M365 Agents SDK (microsoft/Agents-for-python#448) to support this natively so we can drop this patch altogether.
| import uuid | ||
|
|
||
| import pytest | ||
| from httpx import ASGITransport, AsyncClient |
There was a problem hiding this comment.
as discussed, lets remove httpx unless there is a need for it. aiohttp or requests should be good enough
There was a problem hiding this comment.
Addressed it
… httpx) Review feedback on PR #47489: - MSAL FMI patch: switch DefaultAzureCredential -> ManagedIdentityCredential. In a hosted container the agentic token must come from the container's managed identity; ManagedIdentityCredential supports the FMI identity_config exchange but has no credential fallback chain, so it cannot silently pick up an unintended identity (e.g. a developer's Azure CLI login). Removes the now-unused IDENTITY_CONFIG_KEY / MANAGED_IDENTITY_CLIENT_ID_KEY constants. - Mark the overridden-signature tenant_id argument as intentionally unused (_tenant_id) instead of del tenant_id. - Drop the httpx test dependency: add a tiny in-process ASGI client fixture in conftest (scope/receive/send) and use it across the endpoint tests; remove httpx from dev_requirements.txt and pyproject dev group. Validated: 79 tests pass; endpoint tests pass with httpx blocked; pylint 10.00, mypy clean, black clean.
…ld Analyze + API.md Consistency) - Add committed api.md and api.metadata.yml (were missing; failed API.md Consistency check) - Fix cspell Build Analyze failures: rename docstring var environm->env; add MAIB and TENANTID to cspell ignoreWords
…tserver-activity-protocol
…response_id so activity traces list in Foundry (Bug 5417874)
Initial release of the Activity Protocol host package for Azure AI Foundry hosted agents. Provides Starlette-based HTTP endpoints for Activity Protocol traffic with M365 Agents SDK integration.
Package (azure-ai-agentserver-activity 1.0.0b1):
Samples:
Tests: 26 tests covering routes, session resolution, error classification, tracing, decorator pattern, and ID sanitization edge cases.