fixes ai toolbox connections

This commit is contained in:
ValueOn AG 2026-04-14 13:26:12 +02:00
parent 1230a953bd
commit fe89952252
5 changed files with 50 additions and 13 deletions

View file

@ -1266,11 +1266,27 @@ class AppObjects:
return [] return []
def getUserConnectionById(self, connectionId: str) -> Optional[UserConnection]: def getUserConnectionById(self, connectionId: str) -> Optional[UserConnection]:
"""Get a single UserConnection by ID.""" """Get a single UserConnection by ID or by reference string (connection:authority:username)."""
try: try:
# Try direct UUID lookup first
connections = self.db.getRecordset( connections = self.db.getRecordset(
UserConnection, recordFilter={"id": connectionId} UserConnection, recordFilter={"id": connectionId}
) )
# Fallback: parse "connection:authority:username" format from AI agent
if not connections and connectionId.startswith("connection:"):
parts = connectionId.split(":", 2)
if len(parts) >= 3:
authority = parts[1]
username = parts[2]
allConns = self.db.getRecordset(UserConnection, recordFilter={"externalUsername": username})
for c in (allConns or []):
a = c.get("authority", "")
aVal = a.value if hasattr(a, "value") else str(a)
if aVal == authority:
connections = [c]
break
if connections: if connections:
conn_dict = connections[0] conn_dict = connections[0]
return UserConnection( return UserConnection(

View file

@ -135,6 +135,11 @@ def _createDispatchHandler(actionExecutor, methodName: str, actionName: str):
"""Create an async handler that dispatches to the ActionExecutor.""" """Create an async handler that dispatches to the ActionExecutor."""
async def _handler(args: Dict[str, Any], context: Dict[str, Any]) -> ToolResult: async def _handler(args: Dict[str, Any], context: Dict[str, Any]) -> ToolResult:
try: try:
if context:
if "featureInstanceId" not in args and context.get("featureInstanceId"):
args["featureInstanceId"] = context["featureInstanceId"]
if "mandateId" not in args and context.get("mandateId"):
args["mandateId"] = context["mandateId"]
result = await actionExecutor.executeAction(methodName, actionName, args) result = await actionExecutor.executeAction(methodName, actionName, args)
data = _formatActionResult(result) data = _formatActionResult(result)
return ToolResult( return ToolResult(

View file

@ -44,12 +44,12 @@ def _registerConnectionTools(registry: ToolRegistry, services):
return ToolResult(toolCallId="", toolName="listConnections", success=True, data="No connections available.") return ToolResult(toolCallId="", toolName="listConnections", success=True, data="No connections available.")
lines = [] lines = []
for conn in connections: for conn in connections:
connId = conn.get("id", "") if isinstance(conn, dict) else getattr(conn, "id", "")
authority = conn.get("authority", "?") if isinstance(conn, dict) else getattr(conn, "authority", "?") authority = conn.get("authority", "?") if isinstance(conn, dict) else getattr(conn, "authority", "?")
authorityVal = authority.value if hasattr(authority, "value") else str(authority) authorityVal = authority.value if hasattr(authority, "value") else str(authority)
username = conn.get("externalUsername", "") if isinstance(conn, dict) else getattr(conn, "externalUsername", "") username = conn.get("externalUsername", "") if isinstance(conn, dict) else getattr(conn, "externalUsername", "")
email = conn.get("externalEmail", "") if isinstance(conn, dict) else getattr(conn, "externalEmail", "") email = conn.get("externalEmail", "") if isinstance(conn, dict) else getattr(conn, "externalEmail", "")
ref = f"connection:{authorityVal}:{username}" lines.append(f"- connectionId: {connId} | {authorityVal} | {username} ({email})")
lines.append(f"- {ref} ({email})")
return ToolResult(toolCallId="", toolName="listConnections", success=True, data="\n".join(lines)) return ToolResult(toolCallId="", toolName="listConnections", success=True, data="\n".join(lines))
except Exception as e: except Exception as e:
return ToolResult(toolCallId="", toolName="listConnections", success=False, error=str(e)) return ToolResult(toolCallId="", toolName="listConnections", success=False, error=str(e))
@ -137,7 +137,7 @@ def _registerConnectionTools(registry: ToolRegistry, services):
return ToolResult(toolCallId="", toolName="sendMail", success=False, error=str(e)) return ToolResult(toolCallId="", toolName="sendMail", success=False, error=str(e))
_connToolParams = { _connToolParams = {
"connectionId": {"type": "string", "description": "UserConnection ID"}, "connectionId": {"type": "string", "description": "UserConnection UUID (from listConnections output, e.g. '3fa85f64-...')"},
"service": {"type": "string", "description": "Service name (sharepoint, outlook, drive, etc.)"}, "service": {"type": "string", "description": "Service name (sharepoint, outlook, drive, etc.)"},
} }
@ -177,7 +177,7 @@ def _registerConnectionTools(registry: ToolRegistry, services):
parameters={ parameters={
"type": "object", "type": "object",
"properties": { "properties": {
"connectionId": {"type": "string", "description": "UserConnection ID"}, "connectionId": {"type": "string", "description": "UserConnection UUID (from listConnections output)"},
"to": {"type": "array", "items": {"type": "string"}, "description": "Recipient email addresses"}, "to": {"type": "array", "items": {"type": "string"}, "description": "Recipient email addresses"},
"subject": {"type": "string", "description": "Email subject"}, "subject": {"type": "string", "description": "Email subject"},
"body": {"type": "string", "description": "Email body — plain text or HTML markup"}, "body": {"type": "string", "description": "Email body — plain text or HTML markup"},

View file

@ -267,6 +267,8 @@ class AgentService:
registerCoreTools(registry, self.services) registerCoreTools(registry, self.services)
try: try:
from modules.workflows.processing.shared.methodDiscovery import discoverMethods
discoverMethods(self.services)
from modules.workflows.processing.core.actionExecutor import ActionExecutor from modules.workflows.processing.core.actionExecutor import ActionExecutor
actionExecutor = ActionExecutor(self.services) actionExecutor = ActionExecutor(self.services)
adapter = ActionToolAdapter(actionExecutor) adapter = ActionToolAdapter(actionExecutor)
@ -293,10 +295,14 @@ class AgentService:
userConnections: List[str] = [] userConnections: List[str] = []
try: try:
connectionService = self._getService("connection") chatService = self.services.chat if hasattr(self.services, "chat") else None
if connectionService and hasattr(connectionService, "getConnections"): if chatService and hasattr(chatService, "getUserConnections"):
connections = connectionService.getConnections() or [] connections = chatService.getUserConnections() or []
userConnections = [c.get("authority", "") for c in connections if c.get("authority")] for c in connections:
authority = c.get("authority", "") if isinstance(c, dict) else getattr(c, "authority", "")
authorityVal = authority.value if hasattr(authority, "value") else str(authority)
if authorityVal:
userConnections.append(authorityVal)
except Exception as e: except Exception as e:
logger.debug("Could not resolve user connections for toolbox activation: %s", e) logger.debug("Could not resolve user connections for toolbox activation: %s", e)
@ -370,13 +376,23 @@ class AgentService:
if registry.isValidTool(toolName): if registry.isValidTool(toolName):
activatedCount += 1 activatedCount += 1
continue continue
try:
from modules.serviceCenter.services.serviceAgent.coreTools import registerCoreTools
registerCoreTools(registry, self.services)
if registry.isValidTool(toolName):
activatedCount += 1
logger.info("requestToolbox: re-registered tool '%s' (core) from toolbox '%s'", toolName, toolboxId)
continue
except Exception:
pass
try: try:
from modules.serviceCenter.services.serviceAgent.actionToolAdapter import ActionToolAdapter from modules.serviceCenter.services.serviceAgent.actionToolAdapter import ActionToolAdapter
adapter = ActionToolAdapter(self._getService("actionExecutor")) from modules.workflows.processing.core.actionExecutor import ActionExecutor
adapter = ActionToolAdapter(ActionExecutor(self.services))
adapter.registerAll(registry) adapter.registerAll(registry)
if registry.isValidTool(toolName): if registry.isValidTool(toolName):
activatedCount += 1 activatedCount += 1
logger.info("requestToolbox: re-registered tool '%s' from toolbox '%s'", toolName, toolboxId) logger.info("requestToolbox: re-registered tool '%s' (action) from toolbox '%s'", toolName, toolboxId)
else: else:
logger.warning("requestToolbox: tool '%s' from toolbox '%s' could not be registered", toolName, toolboxId) logger.warning("requestToolbox: tool '%s' from toolbox '%s' could not be registered", toolName, toolboxId)
except Exception as regErr: except Exception as regErr:

View file

@ -149,7 +149,7 @@ def _registerDefaultToolboxes() -> None:
id="email", id="email",
label="Email", label="Email",
description="Send emails or save as draft via Outlook (supports HTML body and file attachments). Use sendMail with draft=true for drafts.", description="Send emails or save as draft via Outlook (supports HTML body and file attachments). Use sendMail with draft=true for drafts.",
requiresConnection="microsoft", requiresConnection="msft",
isDefault=False, isDefault=False,
tools=[ tools=[
"sendMail", "sendMail",
@ -159,7 +159,7 @@ def _registerDefaultToolboxes() -> None:
id="sharepoint", id="sharepoint",
label="SharePoint", label="SharePoint",
description="Access SharePoint sites, lists, and files", description="Access SharePoint sites, lists, and files",
requiresConnection="microsoft", requiresConnection="msft",
isDefault=False, isDefault=False,
tools=[ tools=[
"sharepoint_findDocuments", "sharepoint_readDocuments", "sharepoint_findDocuments", "sharepoint_readDocuments",