- Add tool framework (BaseTool ABC, ToolRegistry, ToolContext/ToolResult) - Implement agentic loop in LLMFactory.stream_with_tools() with max_rounds and timeout - Add 9 Microsoft Graph tools: Calendar (3), Mail (3), Teams (1), Planner (2) - Add Graph Token Manager with Fernet encryption and auto-refresh - Add consent flow endpoints (start/callback/status/revoke) - Add admin tool management endpoints (list/toggle) - Add SSE tool events (tool_start, tool_result, tool_error) in chat endpoint - Frontend: ToolCallCard component, tool call tracking in chat store - Frontend: Admin Integrations tab with per-tool enable/disable - Frontend: Consent banner and M365 connect flow - Migration 008: tool_definitions, user_graph_tokens, message_tool_calls tables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
52 lines
1.4 KiB
Python
52 lines
1.4 KiB
Python
"""
|
|
Base class for Microsoft Graph API tools
|
|
"""
|
|
import logging
|
|
from typing import Any, Dict, Optional
|
|
|
|
import httpx
|
|
|
|
from app.tools.base import BaseTool, ToolContext, ToolResult
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
GRAPH_BASE_URL = "https://graph.microsoft.com/v1.0"
|
|
|
|
|
|
class GraphBaseTool(BaseTool):
|
|
"""Base class providing Graph API request helpers"""
|
|
|
|
@property
|
|
def requires_graph_consent(self) -> bool:
|
|
return True
|
|
|
|
async def _graph_request(
|
|
self,
|
|
method: str,
|
|
path: str,
|
|
context: ToolContext,
|
|
params: Optional[Dict[str, Any]] = None,
|
|
json_body: Optional[Dict[str, Any]] = None,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Make an authenticated request to the Microsoft Graph API.
|
|
Raises httpx.HTTPStatusError on non-2xx responses.
|
|
"""
|
|
url = f"{GRAPH_BASE_URL}{path}"
|
|
headers = {
|
|
"Authorization": f"Bearer {context.graph_token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
async with httpx.AsyncClient(timeout=25.0) as client:
|
|
response = await client.request(
|
|
method=method,
|
|
url=url,
|
|
headers=headers,
|
|
params=params,
|
|
json=json_body,
|
|
)
|
|
response.raise_for_status()
|
|
if response.status_code == 204:
|
|
return {}
|
|
return response.json()
|