2424from agent_framework import (
2525 Agent ,
2626 Message ,
27+ WorkflowEvent ,
28+ WorkflowEventType ,
29+ WorkflowRunState ,
2730)
2831from agent_framework .orchestrations import HandoffBuilder , HandoffAgentUserRequest
2932from agent_framework .openai import OpenAIChatCompletionClient
4548# Token endpoint for Azure Cognitive Services (used for Azure OpenAI)
4649TOKEN_ENDPOINT = "https://cognitiveservices.azure.com/.default"
4750
51+ # Event type constants for type-safe dispatch (avoids string typos)
52+ EVENT_STATUS : WorkflowEventType = "status"
53+ EVENT_REQUEST_INFO : WorkflowEventType = "request_info"
54+ EVENT_OUTPUT : WorkflowEventType = "output"
55+
4856
4957# Harmful content patterns to detect in USER INPUT before processing
5058# This provides proactive content safety by blocking harmful requests at the input layer
@@ -538,6 +546,11 @@ def _get_chat_client(self):
538546 if not azure_endpoint :
539547 raise ValueError ("AZURE_OPENAI_ENDPOINT is required for Foundry mode chat completions" )
540548
549+ def get_token () -> str :
550+ """Token provider callable - invoked for each request to ensure fresh tokens."""
551+ token = self ._credential .get_token (TOKEN_ENDPOINT )
552+ return token .token
553+
541554 model_deployment = app_settings .ai_foundry .model_deployment or app_settings .azure_openai .gpt_model
542555 api_version = app_settings .azure_openai .api_version
543556
@@ -546,20 +559,25 @@ def _get_chat_client(self):
546559 azure_endpoint = azure_endpoint ,
547560 model = model_deployment ,
548561 api_version = api_version ,
549- credential = self . _credential ,
562+ credential = get_token ,
550563 )
551564 else :
552565 # Azure OpenAI Direct mode
553566 endpoint = app_settings .azure_openai .endpoint
554567 if not endpoint :
555568 raise ValueError ("AZURE_OPENAI_ENDPOINT is not configured" )
556569
557- logger .info ("Using Azure OpenAI Direct mode with credential" )
570+ def get_token () -> str :
571+ """Token provider callable - invoked for each request to ensure fresh tokens."""
572+ token = self ._credential .get_token (TOKEN_ENDPOINT )
573+ return token .token
574+
575+ logger .info ("Using Azure OpenAI Direct mode with ad_token_provider" )
558576 self ._chat_client = OpenAIChatCompletionClient (
559577 azure_endpoint = endpoint ,
560578 model = app_settings .azure_openai .gpt_model ,
561579 api_version = app_settings .azure_openai .api_version ,
562- credential = self . _credential ,
580+ credential = get_token ,
563581 )
564582 return self ._chat_client
565583
@@ -738,35 +756,35 @@ async def process_message(
738756 events .append (event )
739757
740758 # Handle different event types from the workflow
741- if event .type == "status" :
759+ if event .type == EVENT_STATUS :
760+ status_name = event .state .name if event .state else str (event .data )
742761 yield {
743762 "type" : "status" ,
744- "content" : event . state . name if hasattr ( event , 'state' ) else str ( event . data ) ,
763+ "content" : status_name ,
745764 "is_final" : False ,
746765 "metadata" : {"conversation_id" : conversation_id }
747766 }
748767
749- elif event .type == "request_info" :
768+ elif event .type == EVENT_REQUEST_INFO :
750769 # Workflow is requesting user input
751770 if isinstance (event .data , HandoffAgentUserRequest ):
752- # Extract conversation history from agent_response.messages (updated API)
753- messages = event .data .agent_response .messages if hasattr (event .data , 'agent_response' ) and event .data .agent_response else []
754- if not isinstance (messages , list ):
755- messages = [messages ] if messages else []
771+ # Extract conversation history from agent_response.messages
772+ agent_resp = event .data .agent_response
773+ messages = list (agent_resp .messages ) if agent_resp and agent_resp .messages else []
756774
757775 conversation_text = "\n " .join ([
758776 f"{ msg .author_name or msg .role .value } : { msg .text } "
759777 for msg in messages
760778 ])
761779
762780 # Get the last message content and filter any system prompt leakage
763- last_msg_content = messages [- 1 ].text if messages else (event . data . agent_response . text if hasattr ( event . data , 'agent_response' ) and event . data . agent_response else "" )
781+ last_msg_content = messages [- 1 ].text if messages else (agent_resp . text if agent_resp else "" )
764782 last_msg_content = _filter_system_prompt_from_response (last_msg_content )
765- last_msg_agent = messages [- 1 ].author_name if messages and hasattr ( messages [ - 1 ], 'author_name' ) else "unknown"
783+ last_msg_agent = messages [- 1 ].author_name if messages else "unknown"
766784
767785 yield {
768786 "type" : "agent_response" ,
769- "agent" : last_msg_agent ,
787+ "agent" : last_msg_agent or "unknown" ,
770788 "content" : last_msg_content ,
771789 "conversation_history" : conversation_text ,
772790 "is_final" : False ,
@@ -775,8 +793,7 @@ async def process_message(
775793 "metadata" : {"conversation_id" : conversation_id }
776794 }
777795
778- elif event .type == "output" :
779- # Final output from the workflow
796+ elif event .type == EVENT_OUTPUT :
780797 conversation = cast (list [Message ], event .data )
781798 if isinstance (conversation , list ) and conversation :
782799 # Get the last assistant message as the final response
@@ -843,37 +860,37 @@ async def send_user_response(
843860 try :
844861 responses = {request_id : user_response }
845862 async for event in self ._workflow .send_responses_streaming (responses ):
846- if event .type == "status" :
863+ if event .type == EVENT_STATUS :
864+ status_name = event .state .name if event .state else str (event .data )
847865 yield {
848866 "type" : "status" ,
849- "content" : event . state . name if hasattr ( event , 'state' ) else str ( event . data ) ,
867+ "content" : status_name ,
850868 "is_final" : False ,
851869 "metadata" : {"conversation_id" : conversation_id }
852870 }
853871
854- elif event .type == "request_info" :
872+ elif event .type == EVENT_REQUEST_INFO :
855873 if isinstance (event .data , HandoffAgentUserRequest ):
856- # Get messages from agent_response (updated API)
857- messages = event .data .agent_response .messages if hasattr (event .data , 'agent_response' ) and event .data .agent_response else []
858- if not isinstance (messages , list ):
859- messages = [messages ] if messages else []
874+ # Get messages from agent_response
875+ agent_resp = event .data .agent_response
876+ messages = list (agent_resp .messages ) if agent_resp and agent_resp .messages else []
860877
861878 # Get the last message content and filter any system prompt leakage
862- last_msg_content = messages [- 1 ].text if messages else (event . data . agent_response . text if hasattr ( event . data , 'agent_response' ) and event . data . agent_response else "" )
879+ last_msg_content = messages [- 1 ].text if messages else (agent_resp . text if agent_resp else "" )
863880 last_msg_content = _filter_system_prompt_from_response (last_msg_content )
864- last_msg_agent = messages [- 1 ].author_name if messages and hasattr ( messages [ - 1 ], 'author_name' ) else "unknown"
881+ last_msg_agent = messages [- 1 ].author_name if messages else "unknown"
865882
866883 yield {
867884 "type" : "agent_response" ,
868- "agent" : last_msg_agent ,
885+ "agent" : last_msg_agent or "unknown" ,
869886 "content" : last_msg_content ,
870887 "is_final" : False ,
871888 "requires_user_input" : True ,
872889 "request_id" : event .request_id ,
873890 "metadata" : {"conversation_id" : conversation_id }
874891 }
875892
876- elif event .type == "output" :
893+ elif event .type == EVENT_OUTPUT :
877894 conversation = cast (list [Message ], event .data )
878895 if isinstance (conversation , list ) and conversation :
879896 assistant_messages = [
0 commit comments