diff --git a/dev/integration/tests/telemetry/test_proactive_span_linking.py b/dev/integration/tests/telemetry/test_proactive_span_linking.py index 45cd27372..d37a93066 100644 --- a/dev/integration/tests/telemetry/test_proactive_span_linking.py +++ b/dev/integration/tests/telemetry/test_proactive_span_linking.py @@ -75,7 +75,6 @@ def _create_scenario() -> AiohttpScenario: app = AgentApplication[TurnState]( options=ApplicationOptions( storage=storage, - adapter=adapter, proactive=ProactiveOptions(), ), authorization=authorization, diff --git a/dev/microsoft-agents-hosting-testing/README.md b/dev/microsoft-agents-hosting-testing/README.md index 8ecb04385..e892a458e 100644 --- a/dev/microsoft-agents-hosting-testing/README.md +++ b/dev/microsoft-agents-hosting-testing/README.md @@ -83,6 +83,25 @@ client to interact with the agent. Auth credentials and general SDK config setti Swap one for the other and your assertions stay the same. +### Existing AgentApplication + +Use `AiohttpScenario.from_app(...)` when the `AgentApplication` is already +constructed. Pass the application's existing `CloudAdapter` to preserve its +middleware, host validation, client factories, and error handling: + +```python +scenario = AiohttpScenario.from_app( + AGENT_APP, + adapter=ADAPTER, + use_jwt_middleware=False, +) +``` + +All arguments after `AGENT_APP` are keyword-only. If `adapter` is omitted, the +scenario creates a default `CloudAdapter` from `AGENT_APP.connection_manager`. +The generated adapter does not inherit configuration from another adapter used +to host the application. + ## AgentClient The client you get from a scenario. Send messages, collect replies, make diff --git a/dev/microsoft-agents-hosting-testing/docs/API.md b/dev/microsoft-agents-hosting-testing/docs/API.md index b657ae78b..f427e9f7e 100644 --- a/dev/microsoft-agents-hosting-testing/docs/API.md +++ b/dev/microsoft-agents-hosting-testing/docs/API.md @@ -74,6 +74,42 @@ async with scenario.run() as factory: ))) ``` +Use `from_app(...)` when the `AgentApplication` already exists: + +```python +AiohttpScenario.from_app( + app: AgentApplication, + *, + adapter: CloudAdapter | None = None, + config: ScenarioConfig | None = None, + use_jwt_middleware: bool = True, + sdk_config: dict | None = None, +) +``` + +All arguments after `app` are keyword-only. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `app` | `AgentApplication` | *(required)* | Existing agent application to host | +| `adapter` | `CloudAdapter \| None` | `None` | Adapter used to host the application; when omitted, one is created from `app.connection_manager` | +| `config` | `ScenarioConfig \| None` | `None` | Scenario-level settings (ports, env file, etc.) | +| `use_jwt_middleware` | `bool` | `True` | Enable JWT auth middleware | +| `sdk_config` | `dict \| None` | `None` | SDK configuration used for client authentication | + +```python +scenario = AiohttpScenario.from_app( + AGENT_APP, + adapter=ADAPTER, + use_jwt_middleware=False, +) +``` + +Pass the application's production adapter when the test must preserve adapter +middleware, host validation, client factories, or error handling. The default +adapter only reuses the application's connection manager and does not inherit +configuration from another adapter used to host the application. + ### ActivityHandlerScenario ```python diff --git a/dev/microsoft-agents-hosting-testing/docs/README.md b/dev/microsoft-agents-hosting-testing/docs/README.md index 23aa30cc9..344573a1f 100644 --- a/dev/microsoft-agents-hosting-testing/docs/README.md +++ b/dev/microsoft-agents-hosting-testing/docs/README.md @@ -83,6 +83,25 @@ client to interact with the agent. Auth credentials and general SDK config setti Swap one for the other and your assertions stay the same. +### Existing AgentApplication + +Use `AiohttpScenario.from_app(...)` when the `AgentApplication` is already +constructed. Pass the application's existing `CloudAdapter` to preserve its +middleware, host validation, client factories, and error handling: + +```python +scenario = AiohttpScenario.from_app( + AGENT_APP, + adapter=ADAPTER, + use_jwt_middleware=False, +) +``` + +All arguments after `AGENT_APP` are keyword-only. If `adapter` is omitted, the +scenario creates a default `CloudAdapter` from `AGENT_APP.connection_manager`. +The generated adapter does not inherit configuration from another adapter used +to host the application. + ## AgentClient The client you get from a scenario. Send messages, collect replies, make diff --git a/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/aiohttp_scenario.py b/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/aiohttp_scenario.py index ca382f2c4..98590ebf2 100644 --- a/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/aiohttp_scenario.py +++ b/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/aiohttp_scenario.py @@ -9,9 +9,12 @@ web server. Use :meth:`AiohttpScenario.from_app` when a sample or test already defines -module-level AgentApplication components, and use :meth:`AiohttpScenario.create` -when the test wants the scenario to create the standard storage, adapter, -authorization, and connection components before registering handlers. +module-level AgentApplication components. Pass its hosting ``CloudAdapter`` to +preserve production adapter configuration, or omit it to create a default +adapter from the application's connection manager. Use +:meth:`AiohttpScenario.create` when the test wants the scenario to create the +standard storage, adapter, authorization, and connection components before +registering handlers. """ from __future__ import annotations @@ -56,6 +59,7 @@ logger = logging.getLogger(__name__) + @dataclass(frozen=True) class AgentEnvironment: """Components used by an in-process AgentApplication scenario. @@ -80,6 +84,7 @@ class AgentEnvironment: storage: Storage connections: Connections + class AiohttpScenario(Scenario): """Scenario that hosts an AgentApplication in-process using aiohttp. @@ -93,11 +98,17 @@ class AiohttpScenario(Scenario): - :meth:`create` builds a standard environment and then calls a setup function to register routes/handlers. - :meth:`from_app` wraps an existing AgentApplication, such as one defined - at module scope in a sample's ``agents.py`` file. + at module scope in a sample's ``agents.py`` file. It accepts the + application's CloudAdapter or creates a default adapter from the + application's connection manager. Example:: - scenario = AiohttpScenario.from_app(AGENT_APP, use_jwt_middleware=False) + scenario = AiohttpScenario.from_app( + AGENT_APP, + adapter=ADAPTER, + use_jwt_middleware=False, + ) async with scenario.client() as client: await client.send("Hello!", wait=0.2) @@ -178,8 +189,10 @@ def _default_env_factory( factory when no custom environment factory is provided. """ env_vars = dotenv_values(config.env_file_path or ".env") - sdk_config = load_configuration_from_env(env_vars) if sdk_config is None else sdk_config - + sdk_config = ( + load_configuration_from_env(env_vars) if sdk_config is None else sdk_config + ) + connection_manager: Connections if omit_connections: connection_manager = ConnectionManager( @@ -188,7 +201,7 @@ def _default_env_factory( "SERVICE_CONNECTION": AgentAuthConfiguration( anonymous_allowed=True, ) - } + }, ) else: connection_manager = MsalConnectionManager(**sdk_config) @@ -197,7 +210,7 @@ def _default_env_factory( adapter = CloudAdapter(connection_manager=connection_manager) authorization = Authorization(storage, connection_manager, **sdk_config) agent_application = AgentApplication[TurnState]( - storage=storage, adapter=adapter, authorization=authorization, **sdk_config + storage=storage, authorization=authorization, **sdk_config ) return AgentEnvironment( @@ -250,17 +263,19 @@ def _env_factory() -> AgentEnvironment: return AiohttpScenario._default_env_factory( config=config, sdk_config=sdk_config, omit_connections=omit_connections ) - + return AiohttpScenario( setup, config, use_jwt_middleware=use_jwt_middleware, env_factory=_env_factory, ) - + @staticmethod def from_app( app: AgentApplication, + *, + adapter: CloudAdapter | None = None, config: ScenarioConfig | None = None, use_jwt_middleware: bool = True, sdk_config: dict | None = None, @@ -269,10 +284,15 @@ def from_app( Use this factory for sample-style modules that already define AgentApplication and supporting components at module scope. The scenario - hosts the provided application through its configured adapter and exposes - the resulting environment for fixtures and inspection. + hosts the provided application through the supplied adapter and exposes + the resulting environment for fixtures and inspection. When ``adapter`` + is omitted, a default CloudAdapter is created from the application's + connection manager. Pass the production adapter when tests need its + middleware, host validation, client factories, or error handling. :param app: AgentApplication instance to host. + :param adapter: Optional CloudAdapter used to host the application. + When omitted, one is created from ``app.connection_manager``. :param config: Optional scenario configuration. :param use_jwt_middleware: Whether to enable JWT middleware on the aiohttp route. @@ -286,11 +306,13 @@ def from_app( if storage is None: raise AttributeError("AgentApplication storage could not be resolved.") + adapter = adapter or CloudAdapter(connection_manager=app.connection_manager) + env = AgentEnvironment( config=sdk_config or {}, agent_application=app, authorization=app.auth, - adapter=app.adapter, + adapter=adapter, storage=cast(Storage, storage), connections=app.connection_manager, ) diff --git a/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/main.py b/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/main.py index e9c4bc75e..982c9283c 100644 --- a/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/main.py +++ b/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/main.py @@ -30,7 +30,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @@ -55,6 +55,7 @@ async def on_message(context: TurnContext, _): try: start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) except Exception as error: diff --git a/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/start_server.py b/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/start_server.py index fd1846a6d..ed3812a3c 100644 --- a/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/start_server.py +++ b/dev/microsoft-agents-hosting-testing/microsoft_agents/hosting/testing/presets/basic/my_agent/src/start_server.py @@ -9,7 +9,9 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, + adapter: CloudAdapter, + auth_configuration: AgentAuthConfiguration, ): async def entry_point(req: Request) -> Response: agent: AgentApplication = req.app["agent_app"] @@ -25,7 +27,7 @@ async def entry_point(req: Request) -> Response: APP.router.add_get("/api/messages", lambda _: Response(status=200)) APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application - APP["adapter"] = agent_application.adapter + APP["adapter"] = adapter try: run_app(APP, host="localhost", port=environ.get("PORT", 3978)) diff --git a/dev/microsoft-agents-hosting-testing/tests/test_aiohttp_scenario.py b/dev/microsoft-agents-hosting-testing/tests/test_aiohttp_scenario.py index 7cffa1b5a..266a8a7b6 100644 --- a/dev/microsoft-agents-hosting-testing/tests/test_aiohttp_scenario.py +++ b/dev/microsoft-agents-hosting-testing/tests/test_aiohttp_scenario.py @@ -5,9 +5,40 @@ import pytest -from microsoft_agents.hosting.testing.aiohttp_scenario import AiohttpScenario, AgentEnvironment +from microsoft_agents.hosting.aiohttp import CloudAdapter +from microsoft_agents.hosting.core import ( + AgentApplication, + AgentAuthConfiguration, + AnonymousTokenProvider, + Authorization, + ConnectionManager, + MemoryStorage, + TurnState, +) +from microsoft_agents.hosting.testing import aiohttp_scenario +from microsoft_agents.hosting.testing.aiohttp_scenario import ( + AiohttpScenario, + AgentEnvironment, +) from microsoft_agents.hosting.testing.core import Scenario, ScenarioConfig + +def _create_agent_application(): + connections = ConnectionManager( + provider_factory=lambda _: AnonymousTokenProvider(), + connections_configurations={ + "SERVICE_CONNECTION": AgentAuthConfiguration(anonymous_allowed=True) + }, + ) + storage = MemoryStorage() + authorization = Authorization(storage, connections) + app = AgentApplication[TurnState]( + storage=storage, + authorization=authorization, + ) + return app, connections + + # ============================================================================ # AgentEnvironment Tests # ============================================================================ @@ -137,6 +168,47 @@ def init_agent(env: AgentEnvironment) -> None: assert scenario._env is None +# ============================================================================ +# AiohttpScenario Existing Application Tests +# ============================================================================ + + +class TestAiohttpScenarioFromApp: + """Tests for constructing a scenario from an existing application.""" + + def test_uses_provided_adapter(self): + """from_app preserves an explicitly supplied CloudAdapter.""" + app, connections = _create_agent_application() + adapter = CloudAdapter(connection_manager=connections) + + scenario = AiohttpScenario.from_app(app, adapter=adapter) + + assert scenario.agent_environment.adapter is adapter + + def test_creates_adapter_from_application_connection_manager(self, monkeypatch): + """from_app creates a default adapter from the application's connections.""" + app, connections = _create_agent_application() + created_adapter = object() + + def create_adapter(*, connection_manager): + assert connection_manager is connections + return created_adapter + + monkeypatch.setattr(aiohttp_scenario, "CloudAdapter", create_adapter) + + scenario = AiohttpScenario.from_app(app) + + assert scenario.agent_environment.adapter is created_adapter + assert scenario.agent_environment.connections is connections + + def test_optional_arguments_are_keyword_only(self): + """from_app rejects positional optional arguments.""" + app, _ = _create_agent_application() + + with pytest.raises(TypeError): + AiohttpScenario.from_app(app, ScenarioConfig()) + + # ============================================================================ # AiohttpScenario Configuration Tests # ============================================================================ diff --git a/libraries/microsoft-agents-hosting-aiohttp/readme.md b/libraries/microsoft-agents-hosting-aiohttp/readme.md index be8c5773d..a4097a706 100644 --- a/libraries/microsoft-agents-hosting-aiohttp/readme.md +++ b/libraries/microsoft-agents-hosting-aiohttp/readme.md @@ -187,7 +187,7 @@ ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @AGENT_APP.activity("message") @@ -198,6 +198,7 @@ async def on_message(context: TurnContext, state: TurnState): start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) ``` diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py index 9f7875d2f..248caba1a 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py @@ -7,9 +7,12 @@ import asyncio import logging +import warnings + from contextlib import nullcontext from copy import copy from functools import partial +from typing_extensions import deprecated import re from typing import ( @@ -81,10 +84,10 @@ class AgentApplication(Agent, Generic[StateT]): _adaptive_card: AdaptiveCard _auth: Authorization _proactive: Proactive | None = None + _turn_error_handlers: list[Callable[[TurnContext, Exception], Awaitable[None]]] _internal_before_turn: list[Callable[[TurnContext, StateT], Awaitable[bool]]] _internal_after_turn: list[Callable[[TurnContext, StateT], Awaitable[bool]]] _route_list: _RouteList[StateT] - _error: Callable[[TurnContext, Exception], Awaitable[None]] | None = None _turn_state_factory: Callable[[], StateT] | None = None _connection_manager: Connections @@ -110,6 +113,7 @@ def __init__( """ self._adaptive_card = AdaptiveCard(self) self._route_list = _RouteList[StateT]() + self._turn_error_handlers = [] self._internal_before_turn = [] self._internal_after_turn = [] @@ -136,6 +140,14 @@ def __init__( self._options = options + if self._options.adapter: + warnings.warn( + "AgentApplication.adapter is obsolete and will be removed in a future release.", + DeprecationWarning, + stacklevel=2, + ) + self._adapter = self._options.adapter + if not self._options.storage: logger.error( "ApplicationOptions.storage is required and was not configured.", @@ -146,9 +158,7 @@ def __init__( """) self._storage = self._options.storage - if options.long_running_messages and ( - not options.adapter or not options.bot_app_id - ): + if options.long_running_messages and not options.bot_app_id: logger.error( "ApplicationOptions.long_running_messages requires an adapter and bot_app_id.", stack_info=True, @@ -158,9 +168,6 @@ def __init__( no adapter or `bot_app_id` was configured. """) - if options.adapter: - self._adapter = options.adapter - self._turn_state_factory = ( options.turn_state_factory or kwargs.get("turn_state_factory", None) @@ -219,11 +226,14 @@ def connection_manager(self) -> Connections: return self._connection_manager @property + @deprecated( + "AgentApplication.adapter is deprecated and will be removed in a future release." + ) def adapter(self) -> ChannelServiceAdapter: """ - The bot's adapter. + The application's channel service adapter. - :return: The channel service adapter for the bot. + :return: The channel service adapter for the application. :rtype: :class:`microsoft_agents.hosting.core.channel_service_adapter.ChannelServiceAdapter` :raises ApplicationError: If the adapter is not configured. """ @@ -799,13 +809,7 @@ async def on_error(context: TurnContext, err: Exception): """ logger.debug(f"Registering the error handler {func.__name__} ") - self._error = func - - if self._adapter: - logger.debug( - f"Registering for adapter {self._adapter.__class__.__name__} the error handler {func.__name__} " - ) - self._adapter.on_turn_error = func + self._turn_error_handlers.append(func) return func @@ -883,11 +887,7 @@ async def _on_turn(self, context: TurnContext): if await self._run_after_turn_middleware(context, turn_state): await turn_state.save(context) return - except ApplicationError as err: - logger.error( - f"An application error occurred in the AgentApplication: {err}", - exc_info=True, - ) + except Exception as err: await self._on_error(context, err) def _remove_mentions(self, context: TurnContext): @@ -1000,17 +1000,18 @@ async def _start_long_running_call( self, context: TurnContext, func: Callable[[TurnContext], Awaitable] ): if ( - self._adapter + context.adapter and ActivityTypes.message == context.activity.type and self._options.long_running_messages + and context.identity is not None ): logger.debug( f"Starting long running call for context: {context.activity.id} with function: {func.__name__}" ) - return await self._adapter.continue_conversation( - reference=context.get_conversation_reference(context.activity), + return await context.adapter.continue_conversation_with_claims( + claims_identity=context.identity, + continuation_activity=context.activity, callback=func, - bot_app_id=self.options.bot_app_id, ) return await func(context) @@ -1030,7 +1031,7 @@ async def __replay_turn(replay_context: TurnContext): async def __replay(act: Activity): - await self._adapter.continue_conversation_with_claims( + await context.adapter.continue_conversation_with_claims( context.identity, act, __replay_turn, @@ -1070,16 +1071,21 @@ def __log_task_result(task: asyncio.Task): return - async def _on_error(self, context: TurnContext, err: ApplicationError) -> None: - if self._error: - logger.info( - f"Calling error handler {self._error.__name__} for error: {err}" - ) - return await self._error(context, err) + async def _on_error(self, context: TurnContext, err: Exception) -> None: + """Handle errors that occur during a turn in the AgentApplication. + + :param context: The turn context in which the error occurred. + :param err: The exception that occurred. + """ logger.error( f"An error occurred in the AgentApplication: {err}", exc_info=True, ) - logger.error(err) - raise err + + for err_func in self._turn_error_handlers: + logger.info(f"Calling error handler {err_func.__name__} for error: {err}") + await err_func(context, err) + + if not self._turn_error_handlers: + raise err diff --git a/libraries/microsoft-agents-hosting-core/readme.md b/libraries/microsoft-agents-hosting-core/readme.md index e0e114afa..f87cc4684 100644 --- a/libraries/microsoft-agents-hosting-core/readme.md +++ b/libraries/microsoft-agents-hosting-core/readme.md @@ -186,7 +186,7 @@ ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @AGENT_APP.activity("message") @@ -197,6 +197,7 @@ async def on_message(context: TurnContext, state: TurnState): start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) ``` diff --git a/test_samples/agentic-test/src/agent.py b/test_samples/agentic-test/src/agent.py index 8d0040e30..d429d09a6 100644 --- a/test_samples/agentic-test/src/agent.py +++ b/test_samples/agentic-test/src/agent.py @@ -37,7 +37,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) diff --git a/test_samples/agentic-test/src/main.py b/test_samples/agentic-test/src/main.py index e5891a9af..fc8eb7fe1 100644 --- a/test_samples/agentic-test/src/main.py +++ b/test_samples/agentic-test/src/main.py @@ -16,10 +16,11 @@ ms_agents_logger.setLevel(logging.DEBUG) -from agent import AGENT_APP, CONNECTION_MANAGER +from agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from start_server import start_server start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/agentic-test/src/start_server.py b/test_samples/agentic-test/src/start_server.py index d76b619eb..1f2e3544b 100644 --- a/test_samples/agentic-test/src/start_server.py +++ b/test_samples/agentic-test/src/start_server.py @@ -9,7 +9,9 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, + adapter: CloudAdapter, + auth_configuration: AgentAuthConfiguration ): async def entry_point(req: Request) -> Response: agent: AgentApplication = req.app["agent_app"] @@ -24,7 +26,7 @@ async def entry_point(req: Request) -> Response: APP.router.add_post("/api/messages", entry_point) APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application - APP["adapter"] = agent_application.adapter + APP["adapter"] = adapter try: run_app(APP, host="localhost", port=environ.get("PORT", 3978)) diff --git a/test_samples/app_style/authorization_agent.py b/test_samples/app_style/authorization_agent.py index b8ee07b9c..00747608a 100644 --- a/test_samples/app_style/authorization_agent.py +++ b/test_samples/app_style/authorization_agent.py @@ -31,7 +31,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @@ -299,5 +299,6 @@ async def on_error(context: TurnContext, error: Exception): start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/app_style/echo_proactive_agent.py b/test_samples/app_style/echo_proactive_agent.py index d5119520a..ca3af8e9f 100644 --- a/test_samples/app_style/echo_proactive_agent.py +++ b/test_samples/app_style/echo_proactive_agent.py @@ -123,7 +123,6 @@ def from_json_to_store_item( authorization = Authorization(storage, connection_manager, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( storage=storage, - adapter=adapter, authorization=authorization, **agents_sdk_config.get("AGENTAPPLICATION", {}), ) diff --git a/test_samples/app_style/empty_agent.py b/test_samples/app_style/empty_agent.py index 76c56c132..709a3bdbb 100644 --- a/test_samples/app_style/empty_agent.py +++ b/test_samples/app_style/empty_agent.py @@ -35,7 +35,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @@ -60,6 +60,7 @@ async def on_message(context: TurnContext, _): try: start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) except Exception as error: diff --git a/test_samples/app_style/mcs_agent.py b/test_samples/app_style/mcs_agent.py index e657eb278..f5d34674c 100644 --- a/test_samples/app_style/mcs_agent.py +++ b/test_samples/app_style/mcs_agent.py @@ -86,7 +86,7 @@ def __init__( # Create the agent instance AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @@ -190,5 +190,6 @@ def _create_client(token: str) -> CopilotClient: # Use the start_server function from shared module start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/app_style/shared/start_server.py b/test_samples/app_style/shared/start_server.py index fd1846a6d..6235fe8ba 100644 --- a/test_samples/app_style/shared/start_server.py +++ b/test_samples/app_style/shared/start_server.py @@ -9,7 +9,9 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, + adapter: CloudAdapter, + auth_configuration: AgentAuthConfiguration ): async def entry_point(req: Request) -> Response: agent: AgentApplication = req.app["agent_app"] @@ -25,7 +27,7 @@ async def entry_point(req: Request) -> Response: APP.router.add_get("/api/messages", lambda _: Response(status=200)) APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application - APP["adapter"] = agent_application.adapter + APP["adapter"] = adapter try: run_app(APP, host="localhost", port=environ.get("PORT", 3978)) diff --git a/test_samples/app_style/streaming_agent.py b/test_samples/app_style/streaming_agent.py index e20f902da..82f9ca96a 100644 --- a/test_samples/app_style/streaming_agent.py +++ b/test_samples/app_style/streaming_agent.py @@ -36,7 +36,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @@ -111,5 +111,6 @@ async def on_error(context: TurnContext, error: Exception): start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/cards/agent.py b/test_samples/cards/agent.py index 6a1bf2bdb..cff7fc081 100644 --- a/test_samples/cards/agent.py +++ b/test_samples/cards/agent.py @@ -54,7 +54,6 @@ app = AgentApplication[TurnState]( storage=storage, - adapter=adapter, authorization=authorization, start_typing_timer=False, remove_recipient_mention=False, @@ -176,6 +175,7 @@ async def on_message(context: TurnContext, _state: TurnState) -> None: if __name__ == "__main__": start_server( agent_application=app, + adapter=adapter, auth_configuration=( connection_manager.get_default_connection_configuration() ), diff --git a/test_samples/cards/start_server.py b/test_samples/cards/start_server.py index 7be62c23c..81f17caa3 100644 --- a/test_samples/cards/start_server.py +++ b/test_samples/cards/start_server.py @@ -15,6 +15,7 @@ def start_server( agent_application: AgentApplication, + adapter: CloudAdapter, auth_configuration: AgentAuthConfiguration, ) -> None: async def entry_point(request: Request) -> Response: @@ -27,6 +28,6 @@ async def entry_point(request: Request) -> Response: web_app.router.add_get("/", lambda _: Response(text="Cards sample")) web_app["agent_configuration"] = auth_configuration web_app["agent_app"] = agent_application - web_app["adapter"] = agent_application.adapter + web_app["adapter"] = adapter run_app(web_app, host="localhost", port=int(environ.get("PORT", 3978))) diff --git a/test_samples/copilot_studio_connector/app.py b/test_samples/copilot_studio_connector/app.py index 92ea69857..b8970c74a 100644 --- a/test_samples/copilot_studio_connector/app.py +++ b/test_samples/copilot_studio_connector/app.py @@ -48,7 +48,6 @@ # Create the agent instance AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config.get("AGENTAPPLICATION", {}), ) @@ -162,7 +161,7 @@ async def entry_point(req: Request) -> Response: CONNECTION_MANAGER.get_default_connection_configuration() ) APP["agent_app"] = AGENT_APP - APP["adapter"] = AGENT_APP.adapter + APP["adapter"] = ADAPTER host = environ.get("HOST", "localhost") port = int(environ.get("PORT", "3978")) diff --git a/test_samples/entra_sidecar/agent.py b/test_samples/entra_sidecar/agent.py index 5d088270d..27d47f486 100644 --- a/test_samples/entra_sidecar/agent.py +++ b/test_samples/entra_sidecar/agent.py @@ -56,7 +56,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @@ -89,7 +89,7 @@ async def _anonymous_auth_middleware(request: Request, handler): return await handler(request) -def start_server(agent_application: AgentApplication): +def start_server(agent_application: AgentApplication, adapter: CloudAdapter): async def entry_point(req: Request) -> Response: agent: AgentApplication = req.app["agent_app"] adapter: CloudAdapter = req.app["adapter"] @@ -114,7 +114,7 @@ async def entry_point(req: Request) -> Response: CONNECTION_MANAGER.get_default_connection_configuration() ) app["agent_app"] = agent_application - app["adapter"] = agent_application.adapter + app["adapter"] = adapter run_app( app, @@ -124,4 +124,4 @@ async def entry_point(req: Request) -> Response: if __name__ == "__main__": - start_server(AGENT_APP) + start_server(AGENT_APP, ADAPTER) diff --git a/test_samples/extensions/extension-starter/src/sample/app.py b/test_samples/extensions/extension-starter/src/sample/app.py index 045391901..0ad4b8fc4 100644 --- a/test_samples/extensions/extension-starter/src/sample/app.py +++ b/test_samples/extensions/extension-starter/src/sample/app.py @@ -28,7 +28,7 @@ ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) diff --git a/test_samples/extensions/extension-starter/src/sample/main.py b/test_samples/extensions/extension-starter/src/sample/main.py index d253252ff..674409de6 100644 --- a/test_samples/extensions/extension-starter/src/sample/main.py +++ b/test_samples/extensions/extension-starter/src/sample/main.py @@ -9,11 +9,12 @@ ms_agents_logger.addHandler(logging.StreamHandler()) ms_agents_logger.setLevel(logging.INFO) -from .app import CONNECTION_MANAGER +from .app import CONNECTION_MANAGER, ADAPTER from .extension_agent import APP from .start_server import start_server start_server( agent_application=APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/extensions/extension-starter/src/sample/start_server.py b/test_samples/extensions/extension-starter/src/sample/start_server.py index d76b619eb..d926497e7 100644 --- a/test_samples/extensions/extension-starter/src/sample/start_server.py +++ b/test_samples/extensions/extension-starter/src/sample/start_server.py @@ -9,7 +9,7 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, adapter: CloudAdapter, auth_configuration: AgentAuthConfiguration ): async def entry_point(req: Request) -> Response: agent: AgentApplication = req.app["agent_app"] @@ -24,7 +24,7 @@ async def entry_point(req: Request) -> Response: APP.router.add_post("/api/messages", entry_point) APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application - APP["adapter"] = agent_application.adapter + APP["adapter"] = adapter try: run_app(APP, host="localhost", port=environ.get("PORT", 3978)) diff --git a/test_samples/extensions/slack-agent/src/app.py b/test_samples/extensions/slack-agent/src/app.py index 88449a43b..ce360047b 100644 --- a/test_samples/extensions/slack-agent/src/app.py +++ b/test_samples/extensions/slack-agent/src/app.py @@ -24,7 +24,6 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) diff --git a/test_samples/extensions/slack-agent/src/main.py b/test_samples/extensions/slack-agent/src/main.py index 5cfc2c0f3..19ea03177 100644 --- a/test_samples/extensions/slack-agent/src/main.py +++ b/test_samples/extensions/slack-agent/src/main.py @@ -8,10 +8,11 @@ ms_agents_logger.setLevel(logging.INFO) from .agent import APP # noqa: E402 (side-effect imports register routes) -from .app import CONNECTION_MANAGER # noqa: E402 +from .app import CONNECTION_MANAGER, ADAPTER # noqa: E402 from .start_server import start_server # noqa: E402 start_server( agent_application=APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/extensions/slack-agent/src/start_server.py b/test_samples/extensions/slack-agent/src/start_server.py index 00aa4516e..cc93a8872 100644 --- a/test_samples/extensions/slack-agent/src/start_server.py +++ b/test_samples/extensions/slack-agent/src/start_server.py @@ -15,6 +15,7 @@ def start_server( agent_application: AgentApplication, + adapter: CloudAdapter, auth_configuration: AgentAuthConfiguration, ) -> None: async def entry_point(req: Request) -> Response: @@ -26,6 +27,6 @@ async def entry_point(req: Request) -> Response: app.router.add_post("/api/messages", entry_point) app["agent_configuration"] = auth_configuration app["agent_app"] = agent_application - app["adapter"] = agent_application.adapter + app["adapter"] = adapter run_app(app, host="localhost", port=environ.get("PORT", 3978)) diff --git a/test_samples/fastapi/authorization_agent.py b/test_samples/fastapi/authorization_agent.py index 8347a3fad..5e465a10d 100644 --- a/test_samples/fastapi/authorization_agent.py +++ b/test_samples/fastapi/authorization_agent.py @@ -45,7 +45,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) @@ -154,7 +154,7 @@ async def messages_handler( return await start_agent_process( request, AGENT_APP, - AGENT_APP.adapter, + ADAPTER, ) diff --git a/test_samples/fastapi/empty_agent.py b/test_samples/fastapi/empty_agent.py index 53007d166..0ffa6094e 100644 --- a/test_samples/fastapi/empty_agent.py +++ b/test_samples/fastapi/empty_agent.py @@ -34,7 +34,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) # Create FastAPI app @@ -72,7 +72,7 @@ async def messages_handler( return await start_agent_process( request, AGENT_APP, - AGENT_APP.adapter, + ADAPTER ) diff --git a/test_samples/hosting_msteams/conversation-agent/src/agent.py b/test_samples/hosting_msteams/conversation-agent/src/agent.py index 87fe6cfad..f6b7bbd9c 100644 --- a/test_samples/hosting_msteams/conversation-agent/src/agent.py +++ b/test_samples/hosting_msteams/conversation-agent/src/agent.py @@ -64,7 +64,6 @@ AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) diff --git a/test_samples/hosting_msteams/conversation-agent/src/main.py b/test_samples/hosting_msteams/conversation-agent/src/main.py index d2c005a4c..b80f346a5 100644 --- a/test_samples/hosting_msteams/conversation-agent/src/main.py +++ b/test_samples/hosting_msteams/conversation-agent/src/main.py @@ -1,11 +1,12 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .agent import AGENT_APP, CONNECTION_MANAGER +from .agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from .start_server import start_server if __name__ == "__main__": start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/hosting_msteams/conversation-agent/src/start_server.py b/test_samples/hosting_msteams/conversation-agent/src/start_server.py index 97792f47d..5c5b53b8d 100644 --- a/test_samples/hosting_msteams/conversation-agent/src/start_server.py +++ b/test_samples/hosting_msteams/conversation-agent/src/start_server.py @@ -15,6 +15,7 @@ def start_server( agent_application: AgentApplication, + adapter: CloudAdapter, auth_configuration, ) -> None: async def entry_point(req: Request) -> Response: @@ -27,6 +28,6 @@ async def entry_point(req: Request) -> Response: app.router.add_get("/api/messages", lambda _: Response(status=200)) app["agent_configuration"] = auth_configuration app["agent_app"] = agent_application - app["adapter"] = agent_application.adapter + app["adapter"] = adapter run_app(app, host="localhost", port=int(environ.get("PORT", 3978))) diff --git a/test_samples/hosting_msteams/graph-clients/src/agent.py b/test_samples/hosting_msteams/graph-clients/src/agent.py index ab07b02fe..3cd63ddb7 100644 --- a/test_samples/hosting_msteams/graph-clients/src/agent.py +++ b/test_samples/hosting_msteams/graph-clients/src/agent.py @@ -32,7 +32,6 @@ AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) diff --git a/test_samples/hosting_msteams/graph-clients/src/main.py b/test_samples/hosting_msteams/graph-clients/src/main.py index 530d25744..bbc024c0c 100644 --- a/test_samples/hosting_msteams/graph-clients/src/main.py +++ b/test_samples/hosting_msteams/graph-clients/src/main.py @@ -1,12 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .agent import AGENT_APP, CONNECTION_MANAGER +from .agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from .start_server import start_server if __name__ == "__main__": start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/hosting_msteams/graph-clients/src/start_server.py b/test_samples/hosting_msteams/graph-clients/src/start_server.py index 97792f47d..5c5b53b8d 100644 --- a/test_samples/hosting_msteams/graph-clients/src/start_server.py +++ b/test_samples/hosting_msteams/graph-clients/src/start_server.py @@ -15,6 +15,7 @@ def start_server( agent_application: AgentApplication, + adapter: CloudAdapter, auth_configuration, ) -> None: async def entry_point(req: Request) -> Response: @@ -27,6 +28,6 @@ async def entry_point(req: Request) -> Response: app.router.add_get("/api/messages", lambda _: Response(status=200)) app["agent_configuration"] = auth_configuration app["agent_app"] = agent_application - app["adapter"] = agent_application.adapter + app["adapter"] = adapter run_app(app, host="localhost", port=int(environ.get("PORT", 3978))) diff --git a/test_samples/hosting_msteams/message-extensions/src/agent.py b/test_samples/hosting_msteams/message-extensions/src/agent.py index 1bb95ce69..335fa754c 100644 --- a/test_samples/hosting_msteams/message-extensions/src/agent.py +++ b/test_samples/hosting_msteams/message-extensions/src/agent.py @@ -60,7 +60,6 @@ AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) diff --git a/test_samples/hosting_msteams/message-extensions/src/main.py b/test_samples/hosting_msteams/message-extensions/src/main.py index d2c005a4c..b80f346a5 100644 --- a/test_samples/hosting_msteams/message-extensions/src/main.py +++ b/test_samples/hosting_msteams/message-extensions/src/main.py @@ -1,11 +1,12 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .agent import AGENT_APP, CONNECTION_MANAGER +from .agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from .start_server import start_server if __name__ == "__main__": start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/hosting_msteams/message-extensions/src/start_server.py b/test_samples/hosting_msteams/message-extensions/src/start_server.py index 39b9f9039..ce3813dc8 100644 --- a/test_samples/hosting_msteams/message-extensions/src/start_server.py +++ b/test_samples/hosting_msteams/message-extensions/src/start_server.py @@ -18,6 +18,7 @@ def start_server( agent_application: AgentApplication, + adapter: CloudAdapter, auth_configuration, ) -> None: @jwt_authorization_decorator @@ -38,6 +39,6 @@ async def health_check(_: Request) -> Response: app.router.add_get("/settings", serve_settings) app["agent_configuration"] = auth_configuration app["agent_app"] = agent_application - app["adapter"] = agent_application.adapter + app["adapter"] = adapter run_app(app, host="localhost", port=int(environ.get("PORT", 3978))) diff --git a/test_samples/hosting_msteams/task-modules/src/agent.py b/test_samples/hosting_msteams/task-modules/src/agent.py index 4e24e0435..f31f36b51 100644 --- a/test_samples/hosting_msteams/task-modules/src/agent.py +++ b/test_samples/hosting_msteams/task-modules/src/agent.py @@ -51,7 +51,6 @@ AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) diff --git a/test_samples/hosting_msteams/task-modules/src/main.py b/test_samples/hosting_msteams/task-modules/src/main.py index d2c005a4c..b80f346a5 100644 --- a/test_samples/hosting_msteams/task-modules/src/main.py +++ b/test_samples/hosting_msteams/task-modules/src/main.py @@ -1,11 +1,12 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .agent import AGENT_APP, CONNECTION_MANAGER +from .agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from .start_server import start_server if __name__ == "__main__": start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/hosting_msteams/task-modules/src/start_server.py b/test_samples/hosting_msteams/task-modules/src/start_server.py index 6d6202776..6d48072ae 100644 --- a/test_samples/hosting_msteams/task-modules/src/start_server.py +++ b/test_samples/hosting_msteams/task-modules/src/start_server.py @@ -28,6 +28,7 @@ async def _auth_unless_public(request: Request, handler): def start_server( agent_application: AgentApplication, + adapter: CloudAdapter, auth_configuration, ) -> None: async def entry_point(req: Request) -> Response: @@ -48,6 +49,6 @@ async def dialog_form(_req: Request) -> Response: app.router.add_get("/dialog-form", dialog_form) app["agent_configuration"] = auth_configuration app["agent_app"] = agent_application - app["adapter"] = agent_application.adapter + app["adapter"] = adapter run_app(app, host="localhost", port=int(environ.get("PORT", 3978))) diff --git a/test_samples/otel/quickstart/src/agent.py b/test_samples/otel/quickstart/src/agent.py index b561857aa..e295af7c8 100644 --- a/test_samples/otel/quickstart/src/agent.py +++ b/test_samples/otel/quickstart/src/agent.py @@ -32,7 +32,7 @@ AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) diff --git a/test_samples/otel/quickstart/src/main.py b/test_samples/otel/quickstart/src/main.py index bfd1ce415..de99da414 100644 --- a/test_samples/otel/quickstart/src/main.py +++ b/test_samples/otel/quickstart/src/main.py @@ -5,10 +5,11 @@ configure_otel_providers(service_name="quickstart_agent") -from .agent import AGENT_APP, CONNECTION_MANAGER +from .agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from .start_server import start_server start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/otel/quickstart/src/start_server.py b/test_samples/otel/quickstart/src/start_server.py index 96e79f9bc..dac9044bf 100644 --- a/test_samples/otel/quickstart/src/start_server.py +++ b/test_samples/otel/quickstart/src/start_server.py @@ -13,7 +13,9 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, + adapter: CloudAdapter, + auth_configuration: AgentAuthConfiguration ): async def entry_point(req: Request) -> Response: @@ -32,6 +34,6 @@ async def entry_point(req: Request) -> Response: APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application - APP["adapter"] = agent_application.adapter + APP["adapter"] = adapter run_app(APP, host="localhost", port=int(environ.get("PORT", 3978))) diff --git a/test_samples/otel/zero-code/src/agent.py b/test_samples/otel/zero-code/src/agent.py index 217ed20d4..a8855c0a2 100644 --- a/test_samples/otel/zero-code/src/agent.py +++ b/test_samples/otel/zero-code/src/agent.py @@ -32,7 +32,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) diff --git a/test_samples/otel/zero-code/src/main.py b/test_samples/otel/zero-code/src/main.py index 9139fe33b..3026f5eeb 100644 --- a/test_samples/otel/zero-code/src/main.py +++ b/test_samples/otel/zero-code/src/main.py @@ -1,10 +1,11 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from .agent import AGENT_APP, CONNECTION_MANAGER +from .agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from .start_server import start_server start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/otel/zero-code/src/start_server.py b/test_samples/otel/zero-code/src/start_server.py index 9f583e4b0..8cd34eb6e 100644 --- a/test_samples/otel/zero-code/src/start_server.py +++ b/test_samples/otel/zero-code/src/start_server.py @@ -13,7 +13,9 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, + adapter: CloudAdapter, + auth_configuration: AgentAuthConfiguration ): async def entry_point(req: Request) -> Response: logger.info("Request received at /api/messages endpoint.") @@ -27,6 +29,6 @@ async def entry_point(req: Request) -> Response: app["agent_configuration"] = auth_configuration app["agent_app"] = agent_application - app["adapter"] = agent_application.adapter + app["adapter"] = adapter run_app(app, host="localhost", port=int(environ.get("PORT", 3978))) diff --git a/test_samples/proactive/proactive_agent.py b/test_samples/proactive/proactive_agent.py index dbd1a04b5..292905bfb 100644 --- a/test_samples/proactive/proactive_agent.py +++ b/test_samples/proactive/proactive_agent.py @@ -72,7 +72,6 @@ AGENT_APP = AgentApplication[TurnState]( options=ApplicationOptions( storage=STORAGE, - adapter=ADAPTER, proactive=ProactiveOptions(), ), authorization=AUTHORIZATION, diff --git a/test_samples/teams_extension/meeting_events_agent.py b/test_samples/teams_extension/meeting_events_agent.py index b5f3deeb7..1cd5e827b 100644 --- a/test_samples/teams_extension/meeting_events_agent.py +++ b/test_samples/teams_extension/meeting_events_agent.py @@ -49,7 +49,6 @@ AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) @@ -110,5 +109,6 @@ async def on_read_receipt( if __name__ == "__main__": start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/teams_extension/message_extensions_agent.py b/test_samples/teams_extension/message_extensions_agent.py index 0e6a7abc3..7328f7a67 100644 --- a/test_samples/teams_extension/message_extensions_agent.py +++ b/test_samples/teams_extension/message_extensions_agent.py @@ -51,7 +51,6 @@ AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) @@ -289,5 +288,6 @@ async def on_fetch_task( if __name__ == "__main__": start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/teams_extension/shared/start_server.py b/test_samples/teams_extension/shared/start_server.py index 0976d010e..8c245a5db 100644 --- a/test_samples/teams_extension/shared/start_server.py +++ b/test_samples/teams_extension/shared/start_server.py @@ -9,7 +9,9 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, + adapter: CloudAdapter, + auth_configuration: AgentAuthConfiguration ): async def entry_point(req: Request) -> Response: agent: AgentApplication = req.app["agent_app"] @@ -21,7 +23,7 @@ async def entry_point(req: Request) -> Response: APP.router.add_get("/api/messages", lambda _: Response(status=200)) APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application - APP["adapter"] = agent_application.adapter + APP["adapter"] = adapter run_app( APP, diff --git a/test_samples/teams_extension/task_modules_agent.py b/test_samples/teams_extension/task_modules_agent.py index 19655cc80..007496923 100644 --- a/test_samples/teams_extension/task_modules_agent.py +++ b/test_samples/teams_extension/task_modules_agent.py @@ -43,7 +43,6 @@ AGENT_APP = AgentApplication[TurnState]( storage=STORAGE, - adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config, ) @@ -276,5 +275,6 @@ async def on_multi_step_submit_email( if __name__ == "__main__": start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/tethered-agent/src/agent.py b/test_samples/tethered-agent/src/agent.py index 93578cdb3..5e5ee60d9 100644 --- a/test_samples/tethered-agent/src/agent.py +++ b/test_samples/tethered-agent/src/agent.py @@ -29,7 +29,7 @@ AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) AGENT_APP = AgentApplication[TurnState]( - storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config + storage=STORAGE, authorization=AUTHORIZATION, **agents_sdk_config ) diff --git a/test_samples/tethered-agent/src/main.py b/test_samples/tethered-agent/src/main.py index 164d898c6..44a0c45af 100644 --- a/test_samples/tethered-agent/src/main.py +++ b/test_samples/tethered-agent/src/main.py @@ -7,10 +7,11 @@ "www.botframework.", ]) -from .agent import AGENT_APP, CONNECTION_MANAGER +from .agent import AGENT_APP, CONNECTION_MANAGER, ADAPTER from .start_server import start_server start_server( agent_application=AGENT_APP, + adapter=ADAPTER, auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), ) diff --git a/test_samples/tethered-agent/src/start_server.py b/test_samples/tethered-agent/src/start_server.py index d76b619eb..1f2e3544b 100644 --- a/test_samples/tethered-agent/src/start_server.py +++ b/test_samples/tethered-agent/src/start_server.py @@ -9,7 +9,9 @@ def start_server( - agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration + agent_application: AgentApplication, + adapter: CloudAdapter, + auth_configuration: AgentAuthConfiguration ): async def entry_point(req: Request) -> Response: agent: AgentApplication = req.app["agent_app"] @@ -24,7 +26,7 @@ async def entry_point(req: Request) -> Response: APP.router.add_post("/api/messages", entry_point) APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application - APP["adapter"] = agent_application.adapter + APP["adapter"] = adapter try: run_app(APP, host="localhost", port=environ.get("PORT", 3978)) diff --git a/tests/_integration/common/testing_environment.py b/tests/_integration/common/testing_environment.py index 562b57ff0..f8fe066af 100644 --- a/tests/_integration/common/testing_environment.py +++ b/tests/_integration/common/testing_environment.py @@ -48,7 +48,7 @@ def get_env(self): # self.authorization = Authorization(self.storage, self.connection_manager, **agents_sdk_config) # self.agent_app = AgentApplication[TurnState]( -# storage=self.storage, adapter=self.adapter, authorization=self.authorization, **agents_sdk_config +# storage=self.storage, authorization=self.authorization, **agents_sdk_config # ) @@ -68,8 +68,5 @@ def __init__(self, mocker): ) self.agent_app = AgentApplication[TurnState]( - storage=self.storage, - adapter=self.adapter, - authorization=self.authorization, - **agents_sdk_config + storage=self.storage, authorization=self.authorization, **agents_sdk_config ) diff --git a/tests/hosting_core/app/test_agent_application.py b/tests/hosting_core/app/test_agent_application.py index f456c0e8d..f93271a6b 100644 --- a/tests/hosting_core/app/test_agent_application.py +++ b/tests/hosting_core/app/test_agent_application.py @@ -276,6 +276,145 @@ def test_init_succeeds_when_authorization_provided_without_connection_manager(): assert app.auth is auth +# --------------------------------------------------------------------------- +# error handlers +# --------------------------------------------------------------------------- + + +def test_error_handlers_are_initialized_per_application(): + first_app = make_app() + second_app = make_app() + + async def handler(context, error): + pass + + first_app.error(handler) + + assert first_app._turn_error_handlers == [handler] + assert second_app._turn_error_handlers == [] + + +def test_error_registers_handlers_in_order_and_returns_handler(): + app = make_app() + + async def first(context, error): + pass + + async def second(context, error): + pass + + assert app.error(first) is first + assert app.error(second) is second + assert app._turn_error_handlers == [first, second] + + +@pytest.mark.asyncio +async def test_on_error_calls_all_handlers_in_registration_order(): + app = make_app() + context = StubTurnContext(_make_event_activity()) + error = RuntimeError("boom") + calls = [] + + async def first(received_context, received_error): + calls.append(("first", received_context, received_error)) + + async def second(received_context, received_error): + calls.append(("second", received_context, received_error)) + + app.error(first) + app.error(second) + + await app._on_error(context, error) + + assert calls == [ + ("first", context, error), + ("second", context, error), + ] + + +@pytest.mark.asyncio +async def test_on_error_reraises_original_error_when_no_handlers_registered(): + app = make_app() + context = StubTurnContext(_make_event_activity()) + error = RuntimeError("boom") + + with pytest.raises(RuntimeError) as exc_info: + await app._on_error(context, error) + + assert exc_info.value is error + + +@pytest.mark.asyncio +async def test_on_error_propagates_handler_error_and_stops_remaining_handlers(): + app = make_app() + context = StubTurnContext(_make_event_activity()) + handler_error = ValueError("handler failed") + calls = [] + + async def failing_handler(received_context, received_error): + calls.append("failing") + raise handler_error + + async def later_handler(received_context, received_error): + calls.append("later") + + app.error(failing_handler) + app.error(later_handler) + + with pytest.raises(ValueError) as exc_info: + await app._on_error(context, RuntimeError("turn failed")) + + assert exc_info.value is handler_error + assert calls == ["failing"] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "error", + [ + ApplicationError("application failed"), + RuntimeError("handler failed"), + ], +) +async def test_on_turn_passes_uncaught_errors_to_registered_handlers(error): + app = _make_integration_app() + context = StubTurnContext(_make_event_activity()) + received = [] + + @app.activity(ActivityTypes.event) + async def failing_route(route_context, state): + raise error + + @app.error + async def on_error(error_context, received_error): + received.append((error_context, received_error)) + + await app.on_turn(context) + + assert received == [(context, error)] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "error", + [ + ApplicationError("application failed"), + RuntimeError("handler failed"), + ], +) +async def test_on_turn_reraises_uncaught_errors_without_registered_handlers(error): + app = _make_integration_app() + + @app.activity(ActivityTypes.event) + async def failing_route(context, state): + raise error + + with pytest.raises(type(error)) as exc_info: + await app.on_turn(StubTurnContext(_make_event_activity())) + + assert exc_info.value is error + + # --------------------------------------------------------------------------- # before_turn / after_turn – registration # ---------------------------------------------------------------------------