Skip to content

feat: add azure-ai-agentserver-activity package (Activity Protocol)#47489

Open
Hameedkunkanoor wants to merge 39 commits into
mainfrom
hameed-kunkanoor/agentserver-activity-protocol
Open

feat: add azure-ai-agentserver-activity package (Activity Protocol)#47489
Hameedkunkanoor wants to merge 39 commits into
mainfrom
hameed-kunkanoor/agentserver-activity-protocol

Conversation

@Hameedkunkanoor

Copy link
Copy Markdown
Contributor

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)
  • 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.

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.
Copilot AI review requested due to automatic review settings June 15, 2026 01:13
@Hameedkunkanoor Hameedkunkanoor changed the title feat: add azure-ai-agentserver-activity package (Activity Protocol host) feat: add azure-ai-agentserver-activity package (Activity Protocol) Jun 15, 2026
@github-actions github-actions Bot added the Hosted Agents sdk/agentserver/* label Jun 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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-activity package with ActivityAgentServerHost, 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.yml and sdk/agentserver/tests.yml to 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, "")
Comment on lines +156 to +160
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)
Comment on lines +6 to +10
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
Comment on lines +21 to +24
`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
…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.
…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.
@Hameedkunkanoor Hameedkunkanoor requested a review from xirzec as a code owner June 30, 2026 02:59
Hameedkunkanoor and others added 13 commits June 30, 2026 10:54
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').
…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.
…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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this in error , odd to be able to pass it and then deleted?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

as discussed, lets remove httpx unless there is a need for it. aiohttp or requests should be good enough

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed it

hameedkunkanoor added 5 commits July 10, 2026 08:02
… 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
…response_id so activity traces list in Foundry (Bug 5417874)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Hosted Agents sdk/agentserver/*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants