diff --git a/.flake8 b/.flake8 index 5b72c2abb..b5305d29d 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,13 @@ [flake8] max-line-length = 88 extend-ignore = E501 -exclude = .venv, frontend, src/backend/tests +exclude = + .venv, + frontend, + src/frontend, + src/backend/tests, + *.tsx, + *.ts, + *.jsx, + *.js ignore = E203, W503, G004, G200, E402 \ No newline at end of file diff --git a/src/backend/v3/api/router.py b/src/backend/v3/api/router.py index 6ff928f3f..7ad99e5ce 100644 --- a/src/backend/v3/api/router.py +++ b/src/backend/v3/api/router.py @@ -5,6 +5,7 @@ from typing import Optional import v3.models.messages as messages +from v3.models.messages import WebsocketMessageType from auth.auth_utils import get_authenticated_user_details from common.database.database_factory import DatabaseFactory from common.models.messages_kernel import ( @@ -335,7 +336,6 @@ async def plan_approval( ): """ Endpoint to receive plan approval or rejection from the user. - --- tags: - Plans @@ -382,7 +382,6 @@ async def plan_approval( 500: description: Internal server error """ - authenticated_user = get_authenticated_user_details(request_headers=request.headers) user_id = authenticated_user["user_principal_id"] if not user_id: @@ -399,23 +398,44 @@ async def plan_approval( orchestration_config.set_approval_result( human_feedback.m_plan_id, human_feedback.approved ) - # orchestration_config.plans[human_feedback.m_plan_id][ - # "plan_id" - # ] = human_feedback.plan_id print("Plan approval received:", human_feedback) - # print( - # "Updated orchestration config:", - # orchestration_config.plans[human_feedback.m_plan_id], - # ) + try: result = await PlanService.handle_plan_approval( human_feedback, user_id ) print("Plan approval processed:", result) + except ValueError as ve: - print(f"ValueError processing plan approval: {ve}") - except Exception as e: - print(f"Error processing plan approval: {e}") + logger.error(f"ValueError processing plan approval: {ve}") + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": "Approval failed due to invalid input.", + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) + + except Exception: + logger.error("Error processing plan approval", exc_info=True) + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": "An unexpected error occurred while processing the approval.", + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) + track_event_if_configured( "PlanApprovalReceived", { @@ -437,6 +457,22 @@ async def plan_approval( ) except Exception as e: logging.error(f"Error processing plan approval: {e}") + try: + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": "An error occurred while processing your approval request.", + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) + except Exception as ws_error: + # Don't let WebSocket send failure break the HTTP response + logging.warning(f"Failed to send WebSocket error: {ws_error}") raise HTTPException(status_code=500, detail="Internal server error") diff --git a/src/backend/v3/models/messages.py b/src/backend/v3/models/messages.py index 4537820d2..52208273d 100644 --- a/src/backend/v3/models/messages.py +++ b/src/backend/v3/models/messages.py @@ -214,3 +214,4 @@ class WebsocketMessageType(str, Enum): USER_CLARIFICATION_RESPONSE = "user_clarification_response" FINAL_RESULT_MESSAGE = "final_result_message" TIMEOUT_NOTIFICATION = "timeout_notification" + ERROR_MESSAGE = "error_message" diff --git a/src/backend/v3/orchestration/orchestration_manager.py b/src/backend/v3/orchestration/orchestration_manager.py index 271a82872..bd5f79cb1 100644 --- a/src/backend/v3/orchestration/orchestration_manager.py +++ b/src/backend/v3/orchestration/orchestration_manager.py @@ -9,6 +9,7 @@ from common.models.messages_kernel import TeamConfiguration from semantic_kernel.agents.orchestration.magentic import MagenticOrchestration from semantic_kernel.agents.runtime import InProcessRuntime +from azure.core.exceptions import ResourceNotFoundError # Create custom execution settings to fix schema issues from semantic_kernel.connectors.ai.open_ai import ( @@ -76,8 +77,11 @@ def _user_aware_agent_callback(user_id: str): """Factory method that creates a callback with captured user_id""" def callback(message: ChatMessageContent): - return agent_response_callback(message, user_id) - + try: + return agent_response_callback(message, user_id) + except Exception as e: + logger = logging.getLogger(f"{__name__}.OrchestrationManager") + logger.error(f"Error in agent response callback: {e}") return callback @staticmethod @@ -85,11 +89,16 @@ def _user_aware_streaming_callback(user_id: str): """Factory method that creates a streaming callback with captured user_id""" async def callback( - streaming_message: StreamingChatMessageContent, is_final: bool + streaming_message: StreamingChatMessageContent, + is_final: bool ): - return await streaming_agent_response_callback( - streaming_message, is_final, user_id - ) + try: + return await streaming_agent_response_callback( + streaming_message, is_final, user_id + ) + except Exception as e: + logger = logging.getLogger(f"{__name__}.OrchestrationManager") + logger.error(f"Error in streaming agent response callback: {e}") return callback @@ -172,14 +181,110 @@ async def run_orchestration(self, user_id, input_task) -> None: message_type=WebsocketMessageType.FINAL_RESULT_MESSAGE, ) self.logger.info(f"Final result sent via WebSocket to user {user_id}") + + except ResourceNotFoundError as e: + self.logger.error(f"Agent not found: {e}") + self.logger.info(f"Error: {e}") + self.logger.info(f"Error type: {type(e).__name__}") + if hasattr(e, "__dict__"): + self.logger.info(f"Error attributes: {e.__dict__}") + self.logger.info("=" * 50) + error_content = "**Attention:** The agent is currently unavailable. Please check if it was deleted or recreated.\n\nIf yes, please create a new plan from the home page." + self.logger.info(f"🔴 Sending error message to user {user_id}: {error_content}") + + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": error_content, + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) + self.logger.info(f"✅ Error message sent via WebSocket to user {user_id}") + except Exception as e: + self.logger.error(f"Error processing final result: {e}") + # Send error message to user + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": "**Attention:** An error occurred while processing the final response.\n\nPlease try creating a new plan from the home page.", + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) + + except RuntimeError as e: + if "did not return any response" in str(e): + self.logger.error(f"Agent failed: {e}") + # Send user-friendly error via WebSocket + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": "**Attention:** I'm having trouble connecting to the agent right now.\n\nPlease try creating a new plan from the home page or try again later.", + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) + else: + self.logger.exception("Unexpected RuntimeError") self.logger.info(f"Error: {e}") self.logger.info(f"Error type: {type(e).__name__}") if hasattr(e, "__dict__"): self.logger.info(f"Error attributes: {e.__dict__}") self.logger.info("=" * 50) + # Fallback error message + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": "**Attention:** Something went wrong.\n\nPlease try creating a new plan from the home page or try again later.", + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) except Exception as e: - self.logger.error(f"Unexpected error: {e}") + self.logger.error(f"🚨 Unexpected error during orchestration: {e}") + self.logger.info(f"Error: {e}") + self.logger.info(f"Error type: {type(e).__name__}") + if hasattr(e, "__dict__"): + self.logger.info(f"Error attributes: {e.__dict__}") + self.logger.info("=" * 50) + + error_content = "**Attention:** Something went wrong.\n\nPlease try creating a new plan from the home page or try again later." + + self.logger.info(f"🔴 Sending error message to user {user_id}: {error_content}") + + await connection_config.send_status_update_async( + { + "type": WebsocketMessageType.ERROR_MESSAGE, + "data": { + "content": error_content, + "status": "error", + "timestamp": asyncio.get_event_loop().time(), + }, + }, + user_id, + message_type=WebsocketMessageType.ERROR_MESSAGE, + ) + + self.logger.info(f"✅ Error message sent via WebSocket to user {user_id}") + finally: await runtime.stop_when_idle() diff --git a/src/frontend/src/models/enums.tsx b/src/frontend/src/models/enums.tsx index 9c5be061f..6f0deac9a 100644 --- a/src/frontend/src/models/enums.tsx +++ b/src/frontend/src/models/enums.tsx @@ -252,11 +252,13 @@ export enum WebsocketMessageType { REPLAN_APPROVAL_RESPONSE = "replan_approval_response", USER_CLARIFICATION_REQUEST = "user_clarification_request", USER_CLARIFICATION_RESPONSE = "user_clarification_response", - FINAL_RESULT_MESSAGE = "final_result_message" + FINAL_RESULT_MESSAGE = "final_result_message", + ERROR_MESSAGE = 'error_message' } export enum AgentMessageType { HUMAN_AGENT = "Human_Agent", AI_AGENT = "AI_Agent", + SYSTEM_AGENT = "SYSTEM_AGENT" } diff --git a/src/frontend/src/pages/PlanPage.tsx b/src/frontend/src/pages/PlanPage.tsx index 58c37fe6f..6e9c54c31 100644 --- a/src/frontend/src/pages/PlanPage.tsx +++ b/src/frontend/src/pages/PlanPage.tsx @@ -22,7 +22,6 @@ import { APIService } from "../api/apiService"; import { StreamMessage, StreamingPlanUpdate } from "../models"; import { usePlanCancellationAlert } from "../hooks/usePlanCancellationAlert"; import PlanCancellationDialog from "../components/common/PlanCancellationDialog"; - import "../styles/PlanPage.css" // Create API service instance @@ -57,7 +56,20 @@ const PlanPage: React.FC = () => { const [streamingMessageBuffer, setStreamingMessageBuffer] = useState(""); const [showBufferingText, setShowBufferingText] = useState(false); const [agentMessages, setAgentMessages] = useState([]); - + const formatErrorMessage = useCallback((content: string): string => { + // Split content by newlines and add proper indentation + const lines = content.split('\n'); + const formattedLines = lines.map((line, index) => { + if (index === 0) { + return `⚠️ ${line}`; + } else if (line.trim() === '') { + return ''; // Preserve blank lines + } else { + return `      ${line}`; + } + }); + return formattedLines.join('\n'); + }, []); // Plan approval state - track when plan is approved const [planApproved, setPlanApproved] = useState(false); @@ -216,6 +228,9 @@ const PlanPage: React.FC = () => { }, 100); }, []); + const [isProcessing, setIsProcessing] = useState(false); + const [showProcessingMessage, setShowProcessingMessage] = useState(false); + //WebsocketMessageType.PLAN_APPROVAL_REQUEST useEffect(() => { const unsubscribe = webSocketService.on(WebsocketMessageType.PLAN_APPROVAL_REQUEST, (approvalRequest: any) => { @@ -260,6 +275,7 @@ const PlanPage: React.FC = () => { //(WebsocketMessageType.AGENT_MESSAGE_STREAMING useEffect(() => { const unsubscribe = webSocketService.on(WebsocketMessageType.AGENT_MESSAGE_STREAMING, (streamingMessage: any) => { + //console.log('📋 Streaming Message', streamingMessage); // if is final true clear buffer and add final message to agent messages const line = PlanDataService.simplifyHumanClarification(streamingMessage.data.content); @@ -364,6 +380,63 @@ const PlanPage: React.FC = () => { return () => unsubscribe(); }, [scrollToBottom, planData, processAgentMessage, streamingMessageBuffer, setSelectedTeam]); + // WebsocketMessageType.ERROR_MESSAGE + useEffect(() => { + const unsubscribe = webSocketService.on(WebsocketMessageType.ERROR_MESSAGE, (errorMessage: any) => { + console.log('❌ Received ERROR_MESSAGE:', errorMessage); + console.log('❌ Error message data:', errorMessage?.data); + + // Try multiple ways to extract the error message + let errorContent = "An unexpected error occurred. Please try again later."; + + // Check for double-nested data structure + if (errorMessage?.data?.data?.content) { + const content = errorMessage.data.data.content.trim(); + if (content.length > 0) { + errorContent = content; + } + } else if (errorMessage?.data?.content) { + const content = errorMessage.data.content.trim(); + if (content.length > 0) { + errorContent = content; + } + } else if (errorMessage?.content) { + const content = errorMessage.content.trim(); + if (content.length > 0) { + errorContent = content; + } + } else if (typeof errorMessage === 'string') { + const content = errorMessage.trim(); + if (content.length > 0) { + errorContent = content; + } + } + + console.log('❌ Final error content to display:', errorContent); + + const errorAgentMessage: AgentMessageData = { + agent: 'system', + agent_type: AgentMessageType.SYSTEM_AGENT, + timestamp: Date.now(), + steps: [], + next_steps: [], + content: formatErrorMessage(errorContent), + raw_data: errorMessage || '', + }; + + setAgentMessages(prev => [...prev, errorAgentMessage]); + setShowProcessingPlanSpinner(false); + setShowBufferingText(false); + setIsProcessing(false); + setShowProcessingMessage(false); + setSubmittingChatDisableInput(false); + scrollToBottom(); + showToast(errorContent, "error"); + }); + + return () => unsubscribe(); + }, [scrollToBottom, showToast, formatErrorMessage]); + //WebsocketMessageType.AGENT_MESSAGE useEffect(() => { const unsubscribe = webSocketService.on(WebsocketMessageType.AGENT_MESSAGE, (agentMessage: any) => { @@ -510,8 +583,11 @@ const PlanPage: React.FC = () => { const handleApprovePlan = useCallback(async () => { if (!planApprovalRequest) return; + setIsProcessing(true); + setShowProcessingMessage(true); setProcessingApproval(true); let id = showToast("Submitting Approval", "progress"); + try { await apiService.approvePlan({ m_plan_id: planApprovalRequest.id, @@ -519,15 +595,19 @@ const PlanPage: React.FC = () => { approved: true, feedback: 'Plan approved by user' }); - + dismissToast(id); setShowProcessingPlanSpinner(true); setShowApprovalButtons(false); + setIsProcessing(false); + setShowProcessingMessage(false); } catch (error) { dismissToast(id); showToast("Failed to submit approval", "error"); console.error('❌ Failed to approve plan:', error); + setIsProcessing(false); + setShowProcessingMessage(false); } finally { setProcessingApproval(false); } @@ -705,6 +785,7 @@ const PlanPage: React.FC = () => { ) : ( <> + @@ -712,7 +793,7 @@ const PlanPage: React.FC = () => { */} - + void): () => void { + return this.on(WebsocketMessageType.ERROR_MESSAGE, (message: StreamMessage) => { + callback(message.data); + }); + } + private emit(eventType: string, data: any): void { const message: StreamMessage = { type: eventType as any, @@ -250,6 +256,11 @@ class WebSocketService { } break; } + case WebsocketMessageType.ERROR_MESSAGE: { + console.log("Received ERROR_MESSAGE:", message); + this.emit(WebsocketMessageType.ERROR_MESSAGE, message.data); // Emit the data + break; + } case WebsocketMessageType.USER_CLARIFICATION_RESPONSE: case WebsocketMessageType.REPLAN_APPROVAL_REQUEST: case WebsocketMessageType.REPLAN_APPROVAL_RESPONSE: diff --git a/src/mcp_server/uv.lock b/src/mcp_server/uv.lock index 252b2b9b8..0765944fd 100644 --- a/src/mcp_server/uv.lock +++ b/src/mcp_server/uv.lock @@ -77,6 +77,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/d5/3995ed12f941f4a41a273d9b1709282e825ef87ed8eab3833038fee54d59/azure_identity-1.19.0-py3-none-any.whl", hash = "sha256:e3f6558c181692d7509f09de10cca527c7dce426776454fb97df512a46527e81", size = 187587, upload-time = "2024-10-08T15:41:36.423Z" }, ] +[[package]] +name = "backports-tarfile" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, +] + +[[package]] +name = "beartype" +version = "0.22.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/e2/105ceb1704cb80fe4ab3872529ab7b6f365cf7c74f725e6132d0efcf1560/beartype-0.22.6.tar.gz", hash = "sha256:97fbda69c20b48c5780ac2ca60ce3c1bb9af29b3a1a0216898ffabdd523e48f4", size = 1588975, upload-time = "2025-11-20T04:47:14.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/c9/ceecc71fe2c9495a1d8e08d44f5f31f5bca1350d5b2e27a4b6265424f59e/beartype-0.22.6-py3-none-any.whl", hash = "sha256:0584bc46a2ea2a871509679278cda992eadde676c01356ab0ac77421f3c9a093", size = 1324807, upload-time = "2025-11-20T04:47:11.837Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, +] + [[package]] name = "certifi" version = "2025.8.3" @@ -291,6 +318,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/e5/a7b6db64f08cfe065e531ec6b508fa7dac704fab70d05adb5bc0c2c1d1b6/cyclopts-3.22.5-py3-none-any.whl", hash = "sha256:92efb4a094d9812718d7efe0bffa319a19cb661f230dbf24406c18cd8809fb82", size = 84994, upload-time = "2025-07-31T18:18:35.939Z" }, ] +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + [[package]] name = "dnspython" version = "2.7.0" @@ -345,7 +381,7 @@ wheels = [ [[package]] name = "fastmcp" -version = "2.11.3" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, @@ -355,14 +391,17 @@ dependencies = [ { name = "mcp" }, { name = "openapi-core" }, { name = "openapi-pydantic" }, + { name = "platformdirs" }, + { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, { name = "pyperclip" }, { name = "python-dotenv" }, { name = "rich" }, + { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/80/13aec687ec21727b0fe6d26c6fe2febb33ae24e24c980929a706db3a8bc2/fastmcp-2.11.3.tar.gz", hash = "sha256:e8e3834a3e0b513712b8e63a6f0d4cbe19093459a1da3f7fbf8ef2810cfd34e3", size = 2692092, upload-time = "2025-08-11T21:38:46.493Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/3b/c30af894db2c3ec439d0e4168ba7ce705474cabdd0a599033ad9a19ad977/fastmcp-2.13.0.tar.gz", hash = "sha256:57f7b7503363e1babc0d1a13af18252b80366a409e1de85f1256cce66a4bee35", size = 7767346, upload-time = "2025-10-25T12:54:10.957Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/05/63f63ad5b6789a730d94b8cb3910679c5da1ed5b4e38c957140ac9edcf0e/fastmcp-2.11.3-py3-none-any.whl", hash = "sha256:28f22126c90fd36e5de9cc68b9c271b6d832dcf322256f23d220b68afb3352cc", size = 260231, upload-time = "2025-08-11T21:38:44.746Z" }, + { url = "https://files.pythonhosted.org/packages/c0/7f/09942135f506953fc61bb81b9e5eaf50a8eea923b83d9135bd959168ef2d/fastmcp-2.13.0-py3-none-any.whl", hash = "sha256:bdff1399d3b7ebb79286edfd43eb660182432514a5ab8e4cbfb45f1d841d2aa0", size = 367134, upload-time = "2025-10-25T12:54:09.284Z" }, ] [[package]] @@ -456,6 +495,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -474,6 +525,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + [[package]] name = "jsonschema" version = "4.25.1" @@ -516,6 +612,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, ] +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + [[package]] name = "lazy-object-proxy" version = "1.11.0" @@ -558,7 +672,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "azure-identity", specifier = "==1.19.0" }, - { name = "fastmcp", specifier = "==2.11.3" }, + { name = "fastmcp", specifier = "==2.13.0" }, { name = "httpx", specifier = "==0.28.1" }, { name = "pydantic", specifier = "==2.11.7" }, { name = "pydantic-settings", specifier = "==2.6.1" }, @@ -795,6 +909,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" }, ] +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -804,6 +936,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "py-key-value-aio" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "py-key-value-shared" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/35/65310a4818acec0f87a46e5565e341c5a96fc062a9a03495ad28828ff4d7/py_key_value_aio-0.2.8.tar.gz", hash = "sha256:c0cfbb0bd4e962a3fa1a9fa6db9ba9df812899bd9312fa6368aaea7b26008b36", size = 32853, upload-time = "2025-10-24T13:31:04.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/5a/e56747d87a97ad2aff0f3700d77f186f0704c90c2da03bfed9e113dae284/py_key_value_aio-0.2.8-py3-none-any.whl", hash = "sha256:561565547ce8162128fd2bd0b9d70ce04a5f4586da8500cce79a54dfac78c46a", size = 69200, upload-time = "2025-10-24T13:31:03.81Z" }, +] + +[package.optional-dependencies] +disk = [ + { name = "diskcache" }, + { name = "pathvalidate" }, +] +keyring = [ + { name = "keyring" }, +] +memory = [ + { name = "cachetools" }, +] + +[[package]] +name = "py-key-value-shared" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/79/05a1f9280cfa0709479319cbfd2b1c5beb23d5034624f548c83fb65b0b61/py_key_value_shared-0.2.8.tar.gz", hash = "sha256:703b4d3c61af124f0d528ba85995c3c8d78f8bd3d2b217377bd3278598070cc1", size = 8216, upload-time = "2025-10-24T13:31:03.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7a/1726ceaa3343874f322dd83c9ec376ad81f533df8422b8b1e1233a59f8ce/py_key_value_shared-0.2.8-py3-none-any.whl", hash = "sha256:aff1bbfd46d065b2d67897d298642e80e5349eae588c6d11b48452b46b8d46ba", size = 14586, upload-time = "2025-10-24T13:31:02.838Z" }, +] + [[package]] name = "pycparser" version = "2.22" @@ -1031,6 +1201,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -1277,6 +1456,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/7e/8ffc71a8f6833d9c9fb999f5b0ee736b8b159fd66968e05c7afc2dbcd57e/rpds_py-0.27.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:181bc29e59e5e5e6e9d63b143ff4d5191224d355e246b5a48c88ce6b35c4e466", size = 555083, upload-time = "2025-08-07T08:26:19.301Z" }, ] +[[package]] +name = "secretstorage" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/8a/ed6747b1cc723c81f526d4c12c1b1d43d07190e1e8258dbf934392fc850e/secretstorage-3.4.1.tar.gz", hash = "sha256:a799acf5be9fb93db609ebaa4ab6e8f1f3ed5ae640e0fa732bfea59e9c3b50e8", size = 19871, upload-time = "2025-11-11T11:30:23.798Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/6d/24ebb101484f1911a6be6695b76ce43219caa110ebbe07d8c3a5f3106cca/secretstorage-3.4.1-py3-none-any.whl", hash = "sha256:c55d57b4da3de568d8c3af89dad244ab24c35ca1da8625fc1b550edf005ebc41", size = 15301, upload-time = "2025-11-11T11:30:22.618Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1616,3 +1808,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7 wheels = [ { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, ] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]