From 45ff5bb09b09e473c8bb4c9eeba8e21cfe257972 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 2 Oct 2025 09:35:33 -0700 Subject: [PATCH 01/10] Adding agentic test sample --- test_samples/agentic-test/README.md | 3 + test_samples/agentic-test/env.TEMPLATE | 18 ++++ test_samples/agentic-test/requirements.txt | 8 ++ test_samples/agentic-test/src/agent.py | 98 +++++++++++++++++++ test_samples/agentic-test/src/main.py | 24 +++++ test_samples/agentic-test/src/start_server.py | 34 +++++++ 6 files changed, 185 insertions(+) create mode 100644 test_samples/agentic-test/README.md create mode 100644 test_samples/agentic-test/env.TEMPLATE create mode 100644 test_samples/agentic-test/requirements.txt create mode 100644 test_samples/agentic-test/src/agent.py create mode 100644 test_samples/agentic-test/src/main.py create mode 100644 test_samples/agentic-test/src/start_server.py diff --git a/test_samples/agentic-test/README.md b/test_samples/agentic-test/README.md new file mode 100644 index 00000000..376cf6b9 --- /dev/null +++ b/test_samples/agentic-test/README.md @@ -0,0 +1,3 @@ +# Agentic Test Sample + +`CONNECTIONSMAP_1_AUDIENCE` -> set this to the CLIENTID of the ALT connection diff --git a/test_samples/agentic-test/env.TEMPLATE b/test_samples/agentic-test/env.TEMPLATE new file mode 100644 index 00000000..25484dbe --- /dev/null +++ b/test_samples/agentic-test/env.TEMPLATE @@ -0,0 +1,18 @@ +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID= +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET= +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID= +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__ALT_BLUEPRINT_NAME=ALT + +CONNECTIONS__ALT__SETTINGS__CLIENTID= +CONNECTIONS__ALT__SETTINGS__CLIENTSECRET= +CONNECTIONS__ALT__SETTINGS__TENANTID= + +AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__SCOPES=https://graph.microsoft.com/.default +AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__TYPE=AgenticUserAuthorization +AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__ALT_BLUEPRINT_NAME=ALT + +CONNECTIONSMAP_0_SERVICEURL=* +CONNECTIONSMAP_0_CONNECTION=SERVICE_CONNECTION + +CONNECTIONSMAP_1_AUDIENCE= +CONNECTIONSMAP_1_CONNECTION=ALT diff --git a/test_samples/agentic-test/requirements.txt b/test_samples/agentic-test/requirements.txt new file mode 100644 index 00000000..07e15109 --- /dev/null +++ b/test_samples/agentic-test/requirements.txt @@ -0,0 +1,8 @@ +python-dotenv +aiohttp +microsoft-agents-hosting-aiohttp +microsoft-agents-hosting-core +microsoft-agents-authentication-msal +microsoft-agents-activity +msgraph +azure-core \ No newline at end of file diff --git a/test_samples/agentic-test/src/agent.py b/test_samples/agentic-test/src/agent.py new file mode 100644 index 00000000..19724d0b --- /dev/null +++ b/test_samples/agentic-test/src/agent.py @@ -0,0 +1,98 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import sys +import traceback +from dotenv import load_dotenv +from typing import cast, Any + +from azure.core.credentials import AccessToken, TokenCredential +from msgraph.graph_service_client import GraphServiceClient + +from os import environ +from microsoft_agents.hosting.aiohttp import CloudAdapter +from microsoft_agents.hosting.core import ( + Authorization, + AgentApplication, + TurnState, + TurnContext, + MemoryStorage, + AgenticUserAuthorization, +) +from microsoft_agents.authentication.msal import MsalConnectionManager +from microsoft_agents.activity import load_configuration_from_env + +load_dotenv() # robrandao: todo +agents_sdk_config = load_configuration_from_env(environ) + +STORAGE = MemoryStorage() +CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config) +ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) +AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) + +# robrandao: downloader? +AGENT_APP = AgentApplication[TurnState]( + storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config +) + + +@AGENT_APP.activity("message", auth_handlers=["AGENTIC"]) +async def on_message(context: TurnContext, _state: TurnState): + # breakpoint() + handler = AGENT_APP.auth._resolve_handler("AGENTIC") + aau_token = await cast(AgenticUserAuthorization, handler).get_agentic_user_token( + # context, scopes=["https://canary.graph.microsoft.com/.default"] + context, + scopes=["User.Read"], + ) + breakpoint() + + # get_token(context, "AGENTIC") + upn = context.activity.get_agentic_user() + assert upn + + # Create GraphServiceClient using the aau_token + class AgenticTokenCredential(TokenCredential): + def __init__(self, token: str): + self.token = token + + def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: + # Return an AccessToken object + import time + + return AccessToken(token=self.token, expires_on=int(time.time()) + 3600) + + credentials = AgenticTokenCredential(aau_token.token) + client = GraphServiceClient(credentials=credentials) + client.request_adapter.base_url = "https://canary.graph.microsoft.com/v1.0" + + try: + # Get user information + user = await client.me.get() + + # For now, let's just get user info and demonstrate the token is working + # You can extend this later with proper Graph API calls for files + user_name = ( + user.display_name if user and hasattr(user, "display_name") else "User" + ) + user_email = user.mail if user and hasattr(user, "mail") else "No email" + + response_text = f"Hello {user_name}! Your email is {user_email}. The Graph API is working with your agentic user token!" + breakpoint() + + except Exception as e: + response_text = f"Error accessing Microsoft Graph: {str(e)}" + + await context.send_activity(response_text) + + +@AGENT_APP.error +async def on_error(context: TurnContext, error: Exception): + # This check writes out errors to console log .vs. app insights. + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + traceback.print_exc() + + # Send a message to the user + await context.send_activity("The bot encountered an error or bug.") diff --git a/test_samples/agentic-test/src/main.py b/test_samples/agentic-test/src/main.py new file mode 100644 index 00000000..95a25f49 --- /dev/null +++ b/test_samples/agentic-test/src/main.py @@ -0,0 +1,24 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# enable logging for Microsoft Agents library +# for more information, see README.md for Quickstart Agent +import logging + +ms_agents_logger = logging.getLogger("microsoft_agents") +console_handler = logging.StreamHandler() +console_handler.setFormatter( + logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)" + ) +) +ms_agents_logger.addHandler(console_handler) +ms_agents_logger.setLevel(logging.DEBUG) + +from .agent import AGENT_APP, CONNECTION_MANAGER +from .start_server import start_server + +start_server( + agent_application=AGENT_APP, + 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 new file mode 100644 index 00000000..c8a9502e --- /dev/null +++ b/test_samples/agentic-test/src/start_server.py @@ -0,0 +1,34 @@ +from os import environ +from microsoft_agents.hosting.core import AgentApplication, AgentAuthConfiguration +from microsoft_agents.hosting.aiohttp import ( + start_agent_process, + jwt_authorization_middleware, + CloudAdapter, +) +from aiohttp.web import Request, Response, Application, run_app + + +def start_server( + agent_application: AgentApplication, auth_configuration: AgentAuthConfiguration +): + async def entry_point(req: Request) -> Response: + agent: AgentApplication = req.app["agent_app"] + adapter: CloudAdapter = req.app["adapter"] + return await start_agent_process( + req, + agent, + adapter, + ) + + APP = Application(middlewares=[jwt_authorization_middleware]) + # APP = Application(middlewares=]) + # APP = Application() + APP.router.add_post("/api/messages", entry_point) + APP["agent_configuration"] = auth_configuration + APP["agent_app"] = agent_application + APP["adapter"] = agent_application.adapter + + try: + run_app(APP, host="localhost", port=environ.get("PORT", 3978)) + except Exception as error: + raise error From 67daf12d86b9221e581d3fd9a23480326994eb2e Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Thu, 2 Oct 2025 11:40:40 -0700 Subject: [PATCH 02/10] Removed extra logic around Graph token exchange --- test_samples/agentic-test/src/agent.py | 53 ++------------------------ 1 file changed, 3 insertions(+), 50 deletions(-) diff --git a/test_samples/agentic-test/src/agent.py b/test_samples/agentic-test/src/agent.py index 19724d0b..def88c39 100644 --- a/test_samples/agentic-test/src/agent.py +++ b/test_samples/agentic-test/src/agent.py @@ -4,10 +4,6 @@ import sys import traceback from dotenv import load_dotenv -from typing import cast, Any - -from azure.core.credentials import AccessToken, TokenCredential -from msgraph.graph_service_client import GraphServiceClient from os import environ from microsoft_agents.hosting.aiohttp import CloudAdapter @@ -17,7 +13,6 @@ TurnState, TurnContext, MemoryStorage, - AgenticUserAuthorization, ) from microsoft_agents.authentication.msal import MsalConnectionManager from microsoft_agents.activity import load_configuration_from_env @@ -38,52 +33,10 @@ @AGENT_APP.activity("message", auth_handlers=["AGENTIC"]) async def on_message(context: TurnContext, _state: TurnState): - # breakpoint() - handler = AGENT_APP.auth._resolve_handler("AGENTIC") - aau_token = await cast(AgenticUserAuthorization, handler).get_agentic_user_token( - # context, scopes=["https://canary.graph.microsoft.com/.default"] - context, - scopes=["User.Read"], + aau_token = await AGENT_APP.auth.get_token(context, "AGENTIC") + await context.send_activity( + f"Acquired agentic user token with length: {len(aau_token.token)}" ) - breakpoint() - - # get_token(context, "AGENTIC") - upn = context.activity.get_agentic_user() - assert upn - - # Create GraphServiceClient using the aau_token - class AgenticTokenCredential(TokenCredential): - def __init__(self, token: str): - self.token = token - - def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken: - # Return an AccessToken object - import time - - return AccessToken(token=self.token, expires_on=int(time.time()) + 3600) - - credentials = AgenticTokenCredential(aau_token.token) - client = GraphServiceClient(credentials=credentials) - client.request_adapter.base_url = "https://canary.graph.microsoft.com/v1.0" - - try: - # Get user information - user = await client.me.get() - - # For now, let's just get user info and demonstrate the token is working - # You can extend this later with proper Graph API calls for files - user_name = ( - user.display_name if user and hasattr(user, "display_name") else "User" - ) - user_email = user.mail if user and hasattr(user, "mail") else "No email" - - response_text = f"Hello {user_name}! Your email is {user_email}. The Graph API is working with your agentic user token!" - breakpoint() - - except Exception as e: - response_text = f"Error accessing Microsoft Graph: {str(e)}" - - await context.send_activity(response_text) @AGENT_APP.error From 9c589b0ad610180880843857880b8956d8233477 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 09:00:40 -0700 Subject: [PATCH 03/10] Basics for closing the loop with agentic work --- .../hosting/core/channel_service_adapter.py | 49 +++++++------- .../channel_service_client_factory_base.py | 4 +- .../core/connector/client/connector_client.py | 9 +-- .../rest_channel_service_client_factory.py | 65 +++++++++++++++++-- 4 files changed, 89 insertions(+), 38 deletions(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py index 244ebcdb..dc27a76f 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py @@ -213,21 +213,21 @@ async def create_conversation( # pylint: disable=arguments-differ claims_identity = self.create_claims_identity(agent_app_id) claims_identity.claims[AuthenticationConstants.SERVICE_URL_CLAIM] = service_url - # Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.) - user_token_client: UserTokenClient = ( - await self._channel_service_client_factory.create_user_token_client( - claims_identity - ) - ) - # Create a turn context and run the pipeline. context = self._create_turn_context( claims_identity, None, - user_token_client, callback, ) + # Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.) + user_token_client: UserTokenClient = ( + await self._channel_service_client_factory.create_user_token_client( + context, claims_identity + ) + ) + context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client + # Create the connector client to use for outbound requests. connector_client: ConnectorClient = ( await self._channel_service_client_factory.create_connector_client( @@ -264,22 +264,21 @@ async def process_proactive( callback: Callable[[TurnContext], Awaitable], ): - # Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.) - user_token_client: UserTokenClient = ( - await self._channel_service_client_factory.create_user_token_client( - claims_identity - ) - ) - # Create a turn context and run the pipeline. context = self._create_turn_context( claims_identity, audience, - user_token_client, callback, activity=continuation_activity, ) + user_token_client: UserTokenClient = ( + await self._channel_service_client_factory.create_user_token_client( + context, claims_identity + ) + ) + context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client + # Create the connector client to use for outbound requests. connector_client: ConnectorClient = ( await self._channel_service_client_factory.create_connector_client( @@ -338,22 +337,22 @@ async def process_activity( ): use_anonymous_auth_callback = True - # Create a UserTokenClient instance for the OAuth flow. - user_token_client: UserTokenClient = ( - await self._channel_service_client_factory.create_user_token_client( - claims_identity, use_anonymous_auth_callback - ) - ) - # Create a turn context and run the pipeline. context = self._create_turn_context( claims_identity, outgoing_audience, - user_token_client, callback, activity=activity, ) + # Create a UserTokenClient instance for the OAuth flow. + user_token_client: UserTokenClient = ( + await self._channel_service_client_factory.create_user_token_client( + context, claims_identity, use_anonymous_auth_callback + ) + ) + context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client + # Create the connector client to use for outbound requests. connector_client: ConnectorClient = ( await self._channel_service_client_factory.create_connector_client( @@ -425,14 +424,12 @@ def _create_turn_context( self, claims_identity: ClaimsIdentity, oauth_scope: str, - user_token_client: UserTokenClientBase, callback: Callable[[TurnContext], Awaitable], activity: Optional[Activity] = None, ) -> TurnContext: context = TurnContext(self, activity, claims_identity) context.turn_state[self.AGENT_IDENTITY_KEY] = claims_identity - context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client context.turn_state[self.AGENT_CALLBACK_HANDLER_KEY] = callback context.turn_state[self.CHANNEL_SERVICE_FACTORY_KEY] = ( self._channel_service_client_factory diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py index d311781d..1e91bf13 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py @@ -6,12 +6,14 @@ ConnectorClientBase, UserTokenClientBase, ) +from microsoft_agents.hosting.core.turn_context import TurnContext class ChannelServiceClientFactoryBase(Protocol): @abstractmethod async def create_connector_client( self, + context: TurnContext, claims_identity: ClaimsIdentity, service_url: str, audience: str, @@ -32,7 +34,7 @@ async def create_connector_client( @abstractmethod async def create_user_token_client( - self, claims_identity: ClaimsIdentity, use_anonymous: bool = False + self, context: TurnContext, claims_identity: ClaimsIdentity, use_anonymous: bool = False ) -> UserTokenClientBase: """ Creates the appropriate UserTokenClientBase instance. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py index f713fadf..2d323f54 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py @@ -193,16 +193,16 @@ async def reply_to_activity( ) raise ValueError("conversationId and activityId are required") + conversation_id = conversation_id[:200] # TODO -> when else url = f"v3/conversations/{conversation_id}/activities/{activity_id}" logger.info( f"Replying to activity: {activity_id} in conversation: {conversation_id}. Activity type is {body.type}" ) + async with self.client.post( url, - json=body.model_dump( - by_alias=True, exclude_unset=True, exclude_none=True, mode="json" - ), + json=body.model_dump(by_alias=True, exclude_unset=True, exclude_none=True, mode="json"), ) as response: result = await response.json() if response.content_length else {} @@ -216,7 +216,8 @@ async def reply_to_activity( logger.info( f"Reply to conversation/activity: {result.get('id')}, {activity_id}" ) - return ResourceResponse.model_validate(result) + + return ResourceResponse.model_validate(result) async def send_to_conversation( self, conversation_id: str, body: Activity diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py index 7c444d89..b8636800 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py @@ -115,18 +115,69 @@ async def create_connector_client( ) async def create_user_token_client( - self, claims_identity: ClaimsIdentity, use_anonymous: bool = False + self, context: TurnContext, claims_identity: ClaimsIdentity, use_anonymous: bool = False ) -> UserTokenClient: if use_anonymous: return UserTokenClient(endpoint=self._token_service_endpoint, token="") + + if context.activity.is_agentic_request(): + logger.info( + "Creating user token client for agentic request to token service endpoint: %s", + self._token_service_endpoint, + ) - token_provider = self._connection_manager.get_token_provider( - claims_identity, self._token_service_endpoint - ) + if not context.identity: + raise ValueError("context.identity is required for agentic activities") - token = await token_provider.get_access_token( - self._token_service_audience, [f"{self._token_service_audience}/.default"] - ) + connection = self._connection_manager.get_token_provider( + context.identity, self._token_service_endpoint + ) + + # TODO: clean up linter + if connection._msal_configuration.ALT_BLUEPRINT_ID: + logger.debug( + "Using alternative blueprint ID for agentic token retrieval: %s", + connection._msal_configuration.ALT_BLUEPRINT_ID, + ) + connection = self._connection_manager.get_connection( + connection._msal_configuration.ALT_BLUEPRINT_ID + ) + + agent_instance_id = context.activity.get_agentic_instance_id() + if not agent_instance_id: + raise ValueError( + "Agent instance ID is required for agentic identity role" + ) + + if context.activity.recipient.role == RoleTypes.agentic_identity: + token, _ = await connection.get_agentic_instance_token( + agent_instance_id + ) + else: + agentic_user = context.activity.get_agentic_user() + if not agentic_user: + raise ValueError("Agentic user is required for agentic user role") + token = await connection.get_agentic_user_token( + agent_instance_id, + agentic_user, + [AuthenticationConstants.APX_PRODUCTION_SCOPE], + ) + + else: + scopes = [f"{self._token_service_audience}/.default"] + + token_provider = self._connection_manager.get_token_provider( + claims_identity, self._token_service_endpoint + ) + + token = await token_provider.get_access_token( + self._token_service_audience, scopes + ) + + if not token: + logger.error("Failed to obtain token for user token client") + raise ValueError("Failed to obtain token for user token client") + return UserTokenClient( endpoint=self._token_service_endpoint, token=token, From 0ae6fcdfd9262169bd87fd9802269799eae88c9a Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 09:01:33 -0700 Subject: [PATCH 04/10] Basics for closing the loop with agentic work --- test_samples/agentic-test/env.TEMPLATE | 10 ---------- test_samples/agentic-test/src/agent.py | 6 ++++++ test_samples/agentic-test/src/main.py | 1 + test_samples/agentic-test/src/start_server.py | 2 -- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/test_samples/agentic-test/env.TEMPLATE b/test_samples/agentic-test/env.TEMPLATE index 25484dbe..5ed5fa5e 100644 --- a/test_samples/agentic-test/env.TEMPLATE +++ b/test_samples/agentic-test/env.TEMPLATE @@ -1,18 +1,8 @@ CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID= CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET= CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID= -CONNECTIONS__SERVICE_CONNECTION__SETTINGS__ALT_BLUEPRINT_NAME=ALT -CONNECTIONS__ALT__SETTINGS__CLIENTID= -CONNECTIONS__ALT__SETTINGS__CLIENTSECRET= -CONNECTIONS__ALT__SETTINGS__TENANTID= - -AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__SCOPES=https://graph.microsoft.com/.default AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__TYPE=AgenticUserAuthorization -AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__AGENTIC__SETTINGS__ALT_BLUEPRINT_NAME=ALT CONNECTIONSMAP_0_SERVICEURL=* CONNECTIONSMAP_0_CONNECTION=SERVICE_CONNECTION - -CONNECTIONSMAP_1_AUDIENCE= -CONNECTIONSMAP_1_CONNECTION=ALT diff --git a/test_samples/agentic-test/src/agent.py b/test_samples/agentic-test/src/agent.py index def88c39..84df480e 100644 --- a/test_samples/agentic-test/src/agent.py +++ b/test_samples/agentic-test/src/agent.py @@ -14,6 +14,10 @@ TurnContext, MemoryStorage, ) +from microsoft_agents.hosting.core.storage import ( + TranscriptLoggerMiddleware, + ConsoleTranscriptLogger, +) from microsoft_agents.authentication.msal import MsalConnectionManager from microsoft_agents.activity import load_configuration_from_env @@ -23,6 +27,7 @@ STORAGE = MemoryStorage() CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config) ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) +ADAPTER.use(TranscriptLoggerMiddleware(ConsoleTranscriptLogger())) AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) # robrandao: downloader? @@ -34,6 +39,7 @@ @AGENT_APP.activity("message", auth_handlers=["AGENTIC"]) async def on_message(context: TurnContext, _state: TurnState): aau_token = await AGENT_APP.auth.get_token(context, "AGENTIC") + await context.send_activity( f"Acquired agentic user token with length: {len(aau_token.token)}" ) diff --git a/test_samples/agentic-test/src/main.py b/test_samples/agentic-test/src/main.py index 95a25f49..085b3385 100644 --- a/test_samples/agentic-test/src/main.py +++ b/test_samples/agentic-test/src/main.py @@ -15,6 +15,7 @@ ms_agents_logger.addHandler(console_handler) ms_agents_logger.setLevel(logging.DEBUG) + from .agent import AGENT_APP, CONNECTION_MANAGER from .start_server import start_server diff --git a/test_samples/agentic-test/src/start_server.py b/test_samples/agentic-test/src/start_server.py index c8a9502e..d76b619e 100644 --- a/test_samples/agentic-test/src/start_server.py +++ b/test_samples/agentic-test/src/start_server.py @@ -21,8 +21,6 @@ async def entry_point(req: Request) -> Response: ) APP = Application(middlewares=[jwt_authorization_middleware]) - # APP = Application(middlewares=]) - # APP = Application() APP.router.add_post("/api/messages", entry_point) APP["agent_configuration"] = auth_configuration APP["agent_app"] = agent_application From 644d0ce4d621e22f1c223b0abc763f2abc25006a Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 09:58:12 -0700 Subject: [PATCH 05/10] Adding conversation id truncation to all conversation operations that depend on it to form the API endpoint --- .../core/connector/client/connector_client.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py index 2d323f54..7fdc6bfb 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py @@ -193,7 +193,7 @@ async def reply_to_activity( ) raise ValueError("conversationId and activityId are required") - conversation_id = conversation_id[:200] # TODO -> when else + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/activities/{activity_id}" logger.info( @@ -236,6 +236,7 @@ async def send_to_conversation( ) raise ValueError("conversationId is required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/activities" logger.info( @@ -272,6 +273,7 @@ async def update_activity( ) raise ValueError("conversationId and activityId are required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/activities/{activity_id}" logger.info( @@ -304,6 +306,7 @@ async def delete_activity(self, conversation_id: str, activity_id: str) -> None: ) raise ValueError("conversationId and activityId are required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/activities/{activity_id}" logger.info( @@ -333,6 +336,7 @@ async def upload_attachment( ) raise ValueError("conversationId is required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/attachments" # Convert the AttachmentData to a dictionary @@ -372,6 +376,7 @@ async def get_conversation_members( ) raise ValueError("conversationId is required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/members" logger.info(f"Getting conversation members for conversation: {conversation_id}") @@ -403,6 +408,7 @@ async def get_conversation_member( ) raise ValueError("conversationId and memberId are required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/members/{member_id}" logger.info( @@ -435,6 +441,7 @@ async def delete_conversation_member( ) raise ValueError("conversationId and memberId are required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/members/{member_id}" logger.info( @@ -465,6 +472,7 @@ async def get_activity_members( ) raise ValueError("conversationId and activityId are required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/activities/{activity_id}/members" logger.info( @@ -508,6 +516,7 @@ async def get_conversation_paged_members( if continuation_token is not None: params["continuationToken"] = continuation_token + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/pagedmembers" logger.info( @@ -541,6 +550,7 @@ async def send_conversation_history( ) raise ValueError("conversationId is required") + conversation_id = conversation_id[:200] url = f"v3/conversations/{conversation_id}/activities/history" logger.info(f"Sending conversation history to conversation: {conversation_id}") From 4f16a92f26da8ee48874df1852e93ac2656fe248 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 09:59:58 -0700 Subject: [PATCH 06/10] Reformatting --- .../hosting/core/channel_service_client_factory_base.py | 5 ++++- .../hosting/core/connector/client/connector_client.py | 4 +++- .../hosting/core/rest_channel_service_client_factory.py | 9 ++++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py index 1e91bf13..e325653c 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_client_factory_base.py @@ -34,7 +34,10 @@ async def create_connector_client( @abstractmethod async def create_user_token_client( - self, context: TurnContext, claims_identity: ClaimsIdentity, use_anonymous: bool = False + self, + context: TurnContext, + claims_identity: ClaimsIdentity, + use_anonymous: bool = False, ) -> UserTokenClientBase: """ Creates the appropriate UserTokenClientBase instance. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py index 7fdc6bfb..3bad7d97 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py @@ -202,7 +202,9 @@ async def reply_to_activity( async with self.client.post( url, - json=body.model_dump(by_alias=True, exclude_unset=True, exclude_none=True, mode="json"), + json=body.model_dump( + by_alias=True, exclude_unset=True, exclude_none=True, mode="json" + ), ) as response: result = await response.json() if response.content_length else {} diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py index b8636800..00d87abe 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py @@ -115,11 +115,14 @@ async def create_connector_client( ) async def create_user_token_client( - self, context: TurnContext, claims_identity: ClaimsIdentity, use_anonymous: bool = False + self, + context: TurnContext, + claims_identity: ClaimsIdentity, + use_anonymous: bool = False, ) -> UserTokenClient: if use_anonymous: return UserTokenClient(endpoint=self._token_service_endpoint, token="") - + if context.activity.is_agentic_request(): logger.info( "Creating user token client for agentic request to token service endpoint: %s", @@ -177,7 +180,7 @@ async def create_user_token_client( if not token: logger.error("Failed to obtain token for user token client") raise ValueError("Failed to obtain token for user token client") - + return UserTokenClient( endpoint=self._token_service_endpoint, token=token, From fe7284dfcd4740d2d77fba15c4573217677c8bb5 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 10:10:50 -0700 Subject: [PATCH 07/10] Logging in sample --- test_samples/agentic-test/src/agent.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test_samples/agentic-test/src/agent.py b/test_samples/agentic-test/src/agent.py index 84df480e..925c9667 100644 --- a/test_samples/agentic-test/src/agent.py +++ b/test_samples/agentic-test/src/agent.py @@ -3,6 +3,7 @@ import sys import traceback +import logging from dotenv import load_dotenv from os import environ @@ -21,6 +22,8 @@ from microsoft_agents.authentication.msal import MsalConnectionManager from microsoft_agents.activity import load_configuration_from_env +logger = logging.getLogger(__name__) + load_dotenv() # robrandao: todo agents_sdk_config = load_configuration_from_env(environ) @@ -39,7 +42,6 @@ @AGENT_APP.activity("message", auth_handlers=["AGENTIC"]) async def on_message(context: TurnContext, _state: TurnState): aau_token = await AGENT_APP.auth.get_token(context, "AGENTIC") - await context.send_activity( f"Acquired agentic user token with length: {len(aau_token.token)}" ) @@ -47,11 +49,7 @@ async def on_message(context: TurnContext, _state: TurnState): @AGENT_APP.error async def on_error(context: TurnContext, error: Exception): - # This check writes out errors to console log .vs. app insights. - # NOTE: In production environment, you should consider logging this to Azure - # application insights. - print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) - traceback.print_exc() + logger.error("[on_turn_error] unhandled error: %s", error) # Send a message to the user - await context.send_activity("The bot encountered an error or bug.") + await context.send_activity("The agent encountered an error.") From d765489bdd2a45eae1bd059099de476c5066211e Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 10:36:36 -0700 Subject: [PATCH 08/10] Refactored _get_agentic_token --- .../rest_channel_service_client_factory.py | 150 ++++++++---------- 1 file changed, 63 insertions(+), 87 deletions(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py index 00d87abe..06b810f2 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py @@ -1,8 +1,8 @@ -import re from typing import Optional import logging from microsoft_agents.activity import RoleTypes +from microsoft_agents.authentication.msal import MsalAuth from microsoft_agents.hosting.core.authorization import ( AuthenticationConstants, AnonymousTokenProvider, @@ -33,6 +33,55 @@ def __init__( self._token_service_endpoint = token_service_endpoint self._token_service_audience = token_service_audience + async def _get_agentic_token(self, context: TurnContext, service_url: str) -> str: + logger.info( + "Creating connector client for agentic request to service_url: %s", + service_url, + ) + + if not context.identity: + raise ValueError("context.identity is required for agentic activities") + + connection = self._connection_manager.get_token_provider( + context.identity, service_url + ) + if not isinstance(connection, MsalAuth): + raise TypeError("Expected MsalAuth connection for agentic activities") + + # TODO: clean up linter + if connection._msal_configuration.ALT_BLUEPRINT_ID: + logger.debug( + "Using alternative blueprint ID for agentic token retrieval: %s", + connection._msal_configuration.ALT_BLUEPRINT_ID, + ) + connection = self._connection_manager.get_connection( + connection._msal_configuration.ALT_BLUEPRINT_ID + ) + + agent_instance_id = context.activity.get_agentic_instance_id() + if not agent_instance_id: + raise ValueError( + "Agent instance ID is required for agentic identity role" + ) + + if context.activity.recipient.role == RoleTypes.agentic_identity: + token, _ = await connection.get_agentic_instance_token( + agent_instance_id + ) + else: + agentic_user = context.activity.get_agentic_user() + if not agentic_user: + raise ValueError("Agentic user is required for agentic user role") + token = await connection.get_agentic_user_token( + agent_instance_id, + agentic_user, + [AuthenticationConstants.APX_PRODUCTION_SCOPE], + ) + + if not token: + raise ValueError("Failed to obtain token for agentic activity") + return token + async def create_connector_client( self, context: TurnContext, @@ -42,6 +91,8 @@ async def create_connector_client( scopes: Optional[list[str]] = None, use_anonymous: bool = False, ) -> ConnectorClientBase: + if not context or not claims_identity: + raise TypeError("context and claims_identity are required") if not service_url: raise TypeError( "RestChannelServiceClientFactory.create_connector_client: service_url can't be None or Empty" @@ -52,50 +103,7 @@ async def create_connector_client( ) if context.activity.is_agentic_request(): - logger.info( - "Creating connector client for agentic request to service_url: %s", - service_url, - ) - - if not context.identity: - raise ValueError("context.identity is required for agentic activities") - - connection = self._connection_manager.get_token_provider( - context.identity, service_url - ) - - # TODO: clean up linter - if connection._msal_configuration.ALT_BLUEPRINT_ID: - logger.debug( - "Using alternative blueprint ID for agentic token retrieval: %s", - connection._msal_configuration.ALT_BLUEPRINT_ID, - ) - connection = self._connection_manager.get_connection( - connection._msal_configuration.ALT_BLUEPRINT_ID - ) - - agent_instance_id = context.activity.get_agentic_instance_id() - if not agent_instance_id: - raise ValueError( - "Agent instance ID is required for agentic identity role" - ) - - if context.activity.recipient.role == RoleTypes.agentic_identity: - token, _ = await connection.get_agentic_instance_token( - agent_instance_id - ) - else: - agentic_user = context.activity.get_agentic_user() - if not agentic_user: - raise ValueError("Agentic user is required for agentic user role") - token = await connection.get_agentic_user_token( - agent_instance_id, - agentic_user, - [AuthenticationConstants.APX_PRODUCTION_SCOPE], - ) - - if not token: - raise ValueError("Failed to obtain token for agentic activity") + token = await self._get_agentic_token(context, service_url) else: token_provider: AccessTokenProviderBase = ( self._connection_manager.get_token_provider( @@ -120,52 +128,20 @@ async def create_user_token_client( claims_identity: ClaimsIdentity, use_anonymous: bool = False, ) -> UserTokenClient: + """Create a UserTokenClient for the given context and claims identity. + + :param context: The TurnContext for the current turn of conversation. + :param claims_identity: The ClaimsIdentity of the user. + :param use_anonymous: Whether to use an anonymous token provider. + """ + if not context or not claims_identity: + raise ValueError("context and claims_identity are required") + if use_anonymous: return UserTokenClient(endpoint=self._token_service_endpoint, token="") if context.activity.is_agentic_request(): - logger.info( - "Creating user token client for agentic request to token service endpoint: %s", - self._token_service_endpoint, - ) - - if not context.identity: - raise ValueError("context.identity is required for agentic activities") - - connection = self._connection_manager.get_token_provider( - context.identity, self._token_service_endpoint - ) - - # TODO: clean up linter - if connection._msal_configuration.ALT_BLUEPRINT_ID: - logger.debug( - "Using alternative blueprint ID for agentic token retrieval: %s", - connection._msal_configuration.ALT_BLUEPRINT_ID, - ) - connection = self._connection_manager.get_connection( - connection._msal_configuration.ALT_BLUEPRINT_ID - ) - - agent_instance_id = context.activity.get_agentic_instance_id() - if not agent_instance_id: - raise ValueError( - "Agent instance ID is required for agentic identity role" - ) - - if context.activity.recipient.role == RoleTypes.agentic_identity: - token, _ = await connection.get_agentic_instance_token( - agent_instance_id - ) - else: - agentic_user = context.activity.get_agentic_user() - if not agentic_user: - raise ValueError("Agentic user is required for agentic user role") - token = await connection.get_agentic_user_token( - agent_instance_id, - agentic_user, - [AuthenticationConstants.APX_PRODUCTION_SCOPE], - ) - + token = await self._get_agentic_token(context, self._token_service_endpoint) else: scopes = [f"{self._token_service_audience}/.default"] From 22cbc6494efe7a0fe51d786a7b46cbf00b61cfb0 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 10:56:42 -0700 Subject: [PATCH 09/10] Cleaning up changes --- .../core/rest_channel_service_client_factory.py | 17 +++++++---------- test_samples/agentic-test/README.md | 3 --- test_samples/agentic-test/requirements.txt | 4 +--- test_samples/agentic-test/src/agent.py | 2 -- 4 files changed, 8 insertions(+), 18 deletions(-) delete mode 100644 test_samples/agentic-test/README.md diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py index 06b810f2..a528c1d2 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py @@ -2,7 +2,6 @@ import logging from microsoft_agents.activity import RoleTypes -from microsoft_agents.authentication.msal import MsalAuth from microsoft_agents.hosting.core.authorization import ( AuthenticationConstants, AnonymousTokenProvider, @@ -45,8 +44,10 @@ async def _get_agentic_token(self, context: TurnContext, service_url: str) -> st connection = self._connection_manager.get_token_provider( context.identity, service_url ) - if not isinstance(connection, MsalAuth): - raise TypeError("Expected MsalAuth connection for agentic activities") + if not hasattr(connection, "_msal_configuration"): + raise TypeError( + "Connection does not support MSAL configuration for agentic token retrieval" + ) # TODO: clean up linter if connection._msal_configuration.ALT_BLUEPRINT_ID: @@ -60,14 +61,10 @@ async def _get_agentic_token(self, context: TurnContext, service_url: str) -> st agent_instance_id = context.activity.get_agentic_instance_id() if not agent_instance_id: - raise ValueError( - "Agent instance ID is required for agentic identity role" - ) + raise ValueError("Agent instance ID is required for agentic identity role") if context.activity.recipient.role == RoleTypes.agentic_identity: - token, _ = await connection.get_agentic_instance_token( - agent_instance_id - ) + token, _ = await connection.get_agentic_instance_token(agent_instance_id) else: agentic_user = context.activity.get_agentic_user() if not agentic_user: @@ -129,7 +126,7 @@ async def create_user_token_client( use_anonymous: bool = False, ) -> UserTokenClient: """Create a UserTokenClient for the given context and claims identity. - + :param context: The TurnContext for the current turn of conversation. :param claims_identity: The ClaimsIdentity of the user. :param use_anonymous: Whether to use an anonymous token provider. diff --git a/test_samples/agentic-test/README.md b/test_samples/agentic-test/README.md deleted file mode 100644 index 376cf6b9..00000000 --- a/test_samples/agentic-test/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Agentic Test Sample - -`CONNECTIONSMAP_1_AUDIENCE` -> set this to the CLIENTID of the ALT connection diff --git a/test_samples/agentic-test/requirements.txt b/test_samples/agentic-test/requirements.txt index 07e15109..2510b84c 100644 --- a/test_samples/agentic-test/requirements.txt +++ b/test_samples/agentic-test/requirements.txt @@ -3,6 +3,4 @@ aiohttp microsoft-agents-hosting-aiohttp microsoft-agents-hosting-core microsoft-agents-authentication-msal -microsoft-agents-activity -msgraph -azure-core \ No newline at end of file +microsoft-agents-activity \ No newline at end of file diff --git a/test_samples/agentic-test/src/agent.py b/test_samples/agentic-test/src/agent.py index 925c9667..efbff47f 100644 --- a/test_samples/agentic-test/src/agent.py +++ b/test_samples/agentic-test/src/agent.py @@ -1,8 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -import sys -import traceback import logging from dotenv import load_dotenv From 37d2518776733850deffe44cb99343b272b6260a Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Mon, 6 Oct 2025 12:20:03 -0700 Subject: [PATCH 10/10] Centralizing conversation id normalization code --- .../core/connector/client/connector_client.py | 31 ++++++++++++------- .../rest_channel_service_client_factory.py | 1 - 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py index 3bad7d97..8156ea58 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/connector_client.py @@ -122,8 +122,12 @@ async def get_attachment(self, attachment_id: str, view_id: str) -> BytesIO: class ConversationsOperations(ConversationsBase): - def __init__(self, client: ClientSession): + def __init__(self, client: ClientSession, **kwargs): self.client = client + self._max_conversation_id_length = kwargs.get("max_conversation_id_length", 200) + + def _normalize_conversation_id(self, conversation_id: str) -> str: + return conversation_id[: self._max_conversation_id_length] async def get_conversations( self, continuation_token: Optional[str] = None @@ -193,7 +197,10 @@ async def reply_to_activity( ) raise ValueError("conversationId and activityId are required") - conversation_id = conversation_id[:200] + print("\n*3") + print(conversation_id) + print("\n*3") + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/activities/{activity_id}" logger.info( @@ -238,7 +245,7 @@ async def send_to_conversation( ) raise ValueError("conversationId is required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/activities" logger.info( @@ -275,7 +282,7 @@ async def update_activity( ) raise ValueError("conversationId and activityId are required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/activities/{activity_id}" logger.info( @@ -308,7 +315,7 @@ async def delete_activity(self, conversation_id: str, activity_id: str) -> None: ) raise ValueError("conversationId and activityId are required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/activities/{activity_id}" logger.info( @@ -338,7 +345,7 @@ async def upload_attachment( ) raise ValueError("conversationId is required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/attachments" # Convert the AttachmentData to a dictionary @@ -378,7 +385,7 @@ async def get_conversation_members( ) raise ValueError("conversationId is required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/members" logger.info(f"Getting conversation members for conversation: {conversation_id}") @@ -410,7 +417,7 @@ async def get_conversation_member( ) raise ValueError("conversationId and memberId are required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/members/{member_id}" logger.info( @@ -443,7 +450,7 @@ async def delete_conversation_member( ) raise ValueError("conversationId and memberId are required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/members/{member_id}" logger.info( @@ -474,7 +481,7 @@ async def get_activity_members( ) raise ValueError("conversationId and activityId are required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/activities/{activity_id}/members" logger.info( @@ -518,7 +525,7 @@ async def get_conversation_paged_members( if continuation_token is not None: params["continuationToken"] = continuation_token - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/pagedmembers" logger.info( @@ -552,7 +559,7 @@ async def send_conversation_history( ) raise ValueError("conversationId is required") - conversation_id = conversation_id[:200] + conversation_id = self._normalize_conversation_id(conversation_id) url = f"v3/conversations/{conversation_id}/activities/history" logger.info(f"Sending conversation history to conversation: {conversation_id}") diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py index a528c1d2..10aecc70 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py @@ -49,7 +49,6 @@ async def _get_agentic_token(self, context: TurnContext, service_url: str) -> st "Connection does not support MSAL configuration for agentic token retrieval" ) - # TODO: clean up linter if connection._msal_configuration.ALT_BLUEPRINT_ID: logger.debug( "Using alternative blueprint ID for agentic token retrieval: %s",