From 6d393f9cf3dd479e30a07ad3f90f21ada8cb04c7 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 3 Dec 2025 11:18:33 +0100
Subject: [PATCH] enhanced voice center
---
modules/connectors/connectorVoiceGoogle.py | 55 +++++++++
modules/interfaces/interfaceVoiceObjects.py | 38 ++++++
modules/routes/routeVoiceGoogle.py | 42 +++++++
modules/workflows/methods/methodAi.py | 42 ++++++-
modules/workflows/methods/methodBase.py | 60 +++++++++-
modules/workflows/methods/methodContext.py | 20 +++-
modules/workflows/methods/methodOutlook.py | 112 ++++++++++++++++--
modules/workflows/methods/methodSharepoint.py | 80 +++++++++++--
8 files changed, 425 insertions(+), 24 deletions(-)
diff --git a/modules/connectors/connectorVoiceGoogle.py b/modules/connectors/connectorVoiceGoogle.py
index 715772d0..faead52a 100644
--- a/modules/connectors/connectorVoiceGoogle.py
+++ b/modules/connectors/connectorVoiceGoogle.py
@@ -403,6 +403,61 @@ class ConnectorGoogleSpeech:
"error": str(e)
}
+ async def detectLanguage(self, text: str) -> Dict:
+ """
+ Detect the language of text using Google Cloud Translation API.
+
+ Args:
+ text: Text to detect language for
+
+ Returns:
+ Dict containing detected language code and confidence
+ """
+ try:
+ if not text.strip():
+ logger.warning("⚠️ Empty text provided for language detection")
+ return {
+ "success": False,
+ "language": "",
+ "error": "Empty text provided"
+ }
+
+ # Use a sample of the text (middle 1000 bytes or full text if smaller)
+ textBytes = text.encode('utf-8')
+ if len(textBytes) > 1000:
+ # Take 1000 bytes from the middle
+ startPos = (len(textBytes) - 1000) // 2
+ textSample = textBytes[startPos:startPos + 1000].decode('utf-8', errors='ignore')
+ else:
+ textSample = text
+
+ logger.info(f"🔍 Detecting language for text sample: '{textSample[:100]}...'")
+
+ # Use translation API with auto-detection (source_language=None)
+ result = self.translate_client.translate(
+ textSample,
+ source_language=None, # Auto-detect
+ target_language='en' # Dummy target, we only need detection
+ )
+
+ detectedLanguage = result.get('detectedSourceLanguage', '')
+
+ logger.info(f"✅ Language detected: {detectedLanguage}")
+
+ return {
+ "success": True,
+ "language": detectedLanguage,
+ "confidence": 1.0 # Google Translation API doesn't provide confidence, assume high
+ }
+
+ except Exception as e:
+ logger.error(f"❌ Google Cloud Language Detection error: {e}")
+ return {
+ "success": False,
+ "language": "",
+ "error": str(e)
+ }
+
async def speechToTranslatedText(self, audioContent: bytes,
fromLanguage: str = "de-DE",
toLanguage: str = "en") -> Dict:
diff --git a/modules/interfaces/interfaceVoiceObjects.py b/modules/interfaces/interfaceVoiceObjects.py
index 87cb1413..cf3a1f12 100644
--- a/modules/interfaces/interfaceVoiceObjects.py
+++ b/modules/interfaces/interfaceVoiceObjects.py
@@ -99,6 +99,44 @@ class VoiceObjects:
# Translation Operations
+ async def detectLanguage(self, text: str) -> Dict[str, Any]:
+ """
+ Detect the language of text using Google Cloud Translation API.
+
+ Args:
+ text: Text to detect language for
+
+ Returns:
+ Dict containing detected language code and confidence
+ """
+ try:
+ logger.info(f"🔍 Language detection request: '{text[:100]}...'")
+
+ if not text.strip():
+ return {
+ "success": False,
+ "language": "",
+ "error": "Empty text provided"
+ }
+
+ connector = self._getGoogleSpeechConnector()
+ result = await connector.detectLanguage(text)
+
+ if result["success"]:
+ logger.info(f"✅ Language detected: {result['language']}")
+ else:
+ logger.warning(f"⚠️ Language detection failed: {result.get('error', 'Unknown error')}")
+
+ return result
+
+ except Exception as e:
+ logger.error(f"❌ Language detection error: {e}")
+ return {
+ "success": False,
+ "language": "",
+ "error": str(e)
+ }
+
async def translateText(self, text: str, sourceLanguage: str = "de",
targetLanguage: str = "en") -> Dict[str, Any]:
"""
diff --git a/modules/routes/routeVoiceGoogle.py b/modules/routes/routeVoiceGoogle.py
index 7f33b19c..605feff7 100644
--- a/modules/routes/routeVoiceGoogle.py
+++ b/modules/routes/routeVoiceGoogle.py
@@ -115,6 +115,48 @@ async def speech_to_text(
detail=f"Speech-to-text processing failed: {str(e)}"
)
+@router.post("/detect-language")
+async def detect_language(
+ text: str = Form(...),
+ currentUser: User = Depends(getCurrentUser)
+):
+ """Detect the language of text using Google Cloud Translation API."""
+ try:
+ logger.info(f"🔍 Language detection request: '{text[:100]}...'")
+
+ if not text.strip():
+ raise HTTPException(
+ status_code=400,
+ detail="Empty text provided for language detection"
+ )
+
+ # Get voice interface
+ voiceInterface = _getVoiceInterface(currentUser)
+
+ # Perform language detection
+ result = await voiceInterface.detectLanguage(text)
+
+ if result["success"]:
+ return {
+ "success": True,
+ "language": result["language"],
+ "confidence": result.get("confidence", 1.0)
+ }
+ else:
+ raise HTTPException(
+ status_code=400,
+ detail=f"Language detection failed: {result.get('error', 'Unknown error')}"
+ )
+
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.error(f"❌ Language detection error: {e}")
+ raise HTTPException(
+ status_code=500,
+ detail=f"Language detection processing failed: {str(e)}"
+ )
+
@router.post("/translate")
async def translate_text(
text: str = Form(...),
diff --git a/modules/workflows/methods/methodAi.py b/modules/workflows/methods/methodAi.py
index c60469c8..f3e6aed6 100644
--- a/modules/workflows/methods/methodAi.py
+++ b/modules/workflows/methods/methodAi.py
@@ -172,11 +172,19 @@ class MethodAi(MethodBase):
if aiResponse.documents and len(aiResponse.documents) > 0:
action_documents = []
for doc in aiResponse.documents:
+ validationMetadata = {
+ "actionType": "ai.process",
+ "resultType": normalized_result_type,
+ "outputFormat": output_format,
+ "hasDocuments": True,
+ "documentCount": len(aiResponse.documents)
+ }
action_documents.append(ActionDocument(
documentName=doc.documentName,
documentData=doc.documentData,
mimeType=doc.mimeType or output_mime_type,
- sourceJson=getattr(doc, 'sourceJson', None) # Preserve source JSON for structure validation
+ sourceJson=getattr(doc, 'sourceJson', None), # Preserve source JSON for structure validation
+ validationMetadata=validationMetadata
))
final_documents = action_documents
@@ -188,10 +196,18 @@ class MethodAi(MethodBase):
extension=extension,
action_name="result"
)
+ validationMetadata = {
+ "actionType": "ai.process",
+ "resultType": normalized_result_type,
+ "outputFormat": output_format,
+ "hasDocuments": False,
+ "contentType": "text"
+ }
action_document = ActionDocument(
documentName=meaningful_name,
documentData=aiResponse.content,
- mimeType=output_mime_type
+ mimeType=output_mime_type,
+ validationMetadata=validationMetadata
)
final_documents = [action_document]
@@ -288,10 +304,20 @@ class MethodAi(MethodBase):
)
from modules.datamodels.datamodelChat import ActionDocument
+ validationMetadata = {
+ "actionType": "ai.webResearch",
+ "prompt": prompt,
+ "urlList": parameters.get("urlList", []),
+ "country": parameters.get("country"),
+ "language": parameters.get("language"),
+ "researchDepth": parameters.get("researchDepth", "general"),
+ "resultFormat": "json"
+ }
actionDocument = ActionDocument(
documentName=meaningfulName,
documentData=result,
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)
return ActionResult.isSuccess(documents=[actionDocument])
@@ -490,11 +516,19 @@ class MethodAi(MethodBase):
rendered_content = self._applyCsvOptions(rendered_content, renderOptions)
from modules.datamodels.datamodelChat import ActionDocument
+ validationMetadata = {
+ "actionType": "ai.convert",
+ "inputFormat": normalizedInputFormat,
+ "outputFormat": normalizedOutputFormat,
+ "hasSourceJson": True,
+ "conversionType": "direct_rendering"
+ }
actionDoc = ActionDocument(
documentName=f"{doc.documentName.rsplit('.', 1)[0] if '.' in doc.documentName else doc.documentName}.{normalizedOutputFormat}",
documentData=rendered_content,
mimeType=mime_type,
- sourceJson=jsonData # Preserve source JSON for structure validation
+ sourceJson=jsonData, # Preserve source JSON for structure validation
+ validationMetadata=validationMetadata
)
return ActionResult.isSuccess(documents=[actionDoc])
diff --git a/modules/workflows/methods/methodBase.py b/modules/workflows/methods/methodBase.py
index 3d6742aa..5bbe76c0 100644
--- a/modules/workflows/methods/methodBase.py
+++ b/modules/workflows/methods/methodBase.py
@@ -18,6 +18,19 @@ def action(func):
- success: bool
- documents: List[ActionDocument]
- error: str (if success=False)
+
+ REQUIRED: All ActionDocument instances MUST include validationMetadata for content validation
+ and refinement. Without validationMetadata, results cannot be approved.
+
+ Example validationMetadata structure:
+ validationMetadata = {
+ "actionType": "moduleName.actionName",
+ "param1": value1,
+ "param2": value2,
+ # ... other relevant parameters for validation
+ }
+
+ See MethodBase._createValidationMetadata() for a helper method to create standard metadata.
"""
@wraps(func)
async def wrapper(self, parameters: Dict[str, Any], *args, **kwargs):
@@ -26,7 +39,14 @@ def action(func):
return wrapper
class MethodBase:
- """Base class for all methods"""
+ """Base class for all methods
+
+ IMPORTANT: All actions that return ActionDocument instances MUST include validationMetadata.
+ This metadata is required for content validation and refinement. Without it, results cannot
+ be approved by the validation system.
+
+ Use _createValidationMetadata() helper method to create standardized metadata structures.
+ """
def __init__(self, services: Any):
"""Initialize method with services object"""
@@ -168,6 +188,44 @@ class MethodBase:
else:
return str(type_annotation)
+ def _createValidationMetadata(self, actionName: str, **kwargs) -> Dict[str, Any]:
+ """
+ Helper method to create standardized validationMetadata for ActionDocument instances.
+
+ This method ensures all actions include the required validationMetadata structure
+ for content validation and refinement. Without metadata, results cannot be approved.
+
+ Args:
+ actionName: Name of the action (e.g., "readEmails", "uploadDocument")
+ **kwargs: Additional action-specific metadata fields
+
+ Returns:
+ Dictionary with validationMetadata structure including:
+ - actionType: Full action identifier (moduleName.actionName)
+ - All provided kwargs as additional metadata fields
+
+ Example:
+ validationMetadata = self._createValidationMetadata(
+ "readEmails",
+ connectionReference=connectionReference,
+ folder=folder,
+ limit=limit,
+ emailCount=len(emails)
+ )
+
+ ActionDocument(
+ documentName="emails.json",
+ documentData=json.dumps(data),
+ mimeType="application/json",
+ validationMetadata=validationMetadata # REQUIRED
+ )
+ """
+ metadata = {
+ "actionType": f"{self.name}.{actionName}"
+ }
+ metadata.update(kwargs)
+ return metadata
+
def _generateMeaningfulFileName(self, base_name: str, extension: str, workflow_context: Dict[str, Any] = None, action_name: str = None) -> str:
"""
Generate a meaningful file name with round/task/action information.
diff --git a/modules/workflows/methods/methodContext.py b/modules/workflows/methods/methodContext.py
index e974606c..604093bb 100644
--- a/modules/workflows/methods/methodContext.py
+++ b/modules/workflows/methods/methodContext.py
@@ -78,11 +78,19 @@ class MethodContext(MethodBase):
"getDocumentIndex"
)
+ validationMetadata = {
+ "actionType": "context.getDocumentIndex",
+ "resultType": resultType,
+ "workflowId": getattr(workflow, 'id', 'unknown'),
+ "totalDocuments": indexData.get("totalDocuments", 0) if isinstance(indexData, dict) else 0
+ }
+
# Create ActionDocument
document = ActionDocument(
documentName=filename,
documentData=indexContent,
- mimeType="application/json" if resultType == "json" else "text/plain"
+ mimeType="application/json" if resultType == "json" else "text/plain",
+ validationMetadata=validationMetadata
)
return ActionResult.isSuccess(documents=[document])
@@ -313,10 +321,18 @@ class MethodContext(MethodBase):
documentName = f"document_{i+1:03d}_extracted_{extracted.id}.json"
# Store ContentExtracted object in ActionDocument.documentData
+ validationMetadata = {
+ "actionType": "context.extractContent",
+ "documentIndex": i,
+ "extractedId": extracted.id,
+ "partCount": len(extracted.parts) if extracted.parts else 0,
+ "originalFileName": originalDoc.fileName if originalDoc and hasattr(originalDoc, 'fileName') else None
+ }
actionDoc = ActionDocument(
documentName=documentName,
documentData=extracted, # ContentExtracted object
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)
actionDocuments.append(actionDoc)
diff --git a/modules/workflows/methods/methodOutlook.py b/modules/workflows/methods/methodOutlook.py
index fa7b4e47..04d02830 100644
--- a/modules/workflows/methods/methodOutlook.py
+++ b/modules/workflows/methods/methodOutlook.py
@@ -465,11 +465,22 @@ class MethodOutlook(MethodBase):
"timestamp": self.services.utils.timestampGetUtc()
}
+ validationMetadata = {
+ "actionType": "outlook.readEmails",
+ "connectionReference": connectionReference,
+ "folder": folder,
+ "limit": limit,
+ "filter": filter,
+ "emailCount": email_data.get("count", 0),
+ "outputMimeType": outputMimeType
+ }
+
return ActionResult.isSuccess(
documents=[ActionDocument(
documentName=f"outlook_emails_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(result_data, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
@@ -695,12 +706,23 @@ class MethodOutlook(MethodBase):
"timestamp": self.services.utils.timestampGetUtc()
}
+ validationMetadata = {
+ "actionType": "outlook.searchEmails",
+ "connectionReference": connectionReference,
+ "query": query,
+ "folder": folder,
+ "limit": limit,
+ "resultCount": search_result.get("count", 0),
+ "outputMimeType": outputMimeType
+ }
+
return ActionResult(
success=True,
documents=[ActionDocument(
documentName=f"outlook_email_search_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(result_data, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
@@ -818,12 +840,22 @@ class MethodOutlook(MethodBase):
"timestamp": self.services.utils.timestampGetUtc()
}
+ validationMetadata = {
+ "actionType": "outlook.listDrafts",
+ "connectionReference": connectionReference,
+ "folder": folder,
+ "limit": limit,
+ "draftCount": drafts_result.get("count", 0),
+ "outputMimeType": outputMimeType
+ }
+
return ActionResult(
success=True,
documents=[ActionDocument(
documentName=f"outlook_drafts_list_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(result_data, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
@@ -928,12 +960,21 @@ class MethodOutlook(MethodBase):
"timestamp": self.services.utils.timestampGetUtc()
}
+ validationMetadata = {
+ "actionType": "outlook.findDrafts",
+ "connectionReference": connectionReference,
+ "limit": limit,
+ "totalDrafts": drafts_result.get("totalDrafts", 0),
+ "outputMimeType": outputMimeType
+ }
+
return ActionResult(
success=True,
documents=[ActionDocument(
documentName=f"outlook_drafts_found_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(result_data, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
@@ -1069,12 +1110,22 @@ class MethodOutlook(MethodBase):
"timestamp": self.services.utils.timestampGetUtc()
}
+ validationMetadata = {
+ "actionType": "outlook.checkDraftsFolder",
+ "connectionReference": connectionReference,
+ "limit": limit,
+ "totalDrafts": drafts_result.get("totalDrafts", 0),
+ "draftsFolderId": drafts_result.get("draftsFolderId"),
+ "outputMimeType": outputMimeType
+ }
+
return ActionResult(
success=True,
documents=[ActionDocument(
documentName=f"outlook_drafts_folder_check_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(result_data, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
@@ -1624,34 +1675,61 @@ Return JSON:
# Determine overall success status
if successfulEmails == 0:
+ validationMetadata = {
+ "actionType": "outlook.sendDraftEmail",
+ "connectionReference": connectionReference,
+ "totalEmails": totalEmails,
+ "successfulEmails": successfulEmails,
+ "failedEmails": failedEmails,
+ "status": "all_failed"
+ }
return ActionResult.isFailure(
error=f"Failed to send all {totalEmails} email(s)",
documents=[ActionDocument(
documentName=f"sent_mail_confirmation_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(resultData, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
elif failedEmails > 0:
# Partial success
logger.warning(f"Sent {successfulEmails} out of {totalEmails} emails. {failedEmails} failed.")
+ validationMetadata = {
+ "actionType": "outlook.sendDraftEmail",
+ "connectionReference": connectionReference,
+ "totalEmails": totalEmails,
+ "successfulEmails": successfulEmails,
+ "failedEmails": failedEmails,
+ "status": "partial_success"
+ }
return ActionResult(
success=True,
documents=[ActionDocument(
documentName=f"sent_mail_confirmation_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(resultData, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
else:
# All successful
logger.info(f"Successfully sent all {totalEmails} email(s)")
+ validationMetadata = {
+ "actionType": "outlook.sendDraftEmail",
+ "connectionReference": connectionReference,
+ "totalEmails": totalEmails,
+ "successfulEmails": successfulEmails,
+ "failedEmails": failedEmails,
+ "status": "all_successful"
+ }
return ActionResult(
success=True,
documents=[ActionDocument(
documentName=f"sent_mail_confirmation_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(resultData, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
@@ -1693,12 +1771,19 @@ Return JSON:
"status": "ready"
}
+ validationMetadata = {
+ "actionType": "outlook.checkPermissions",
+ "connectionReference": connectionReference,
+ "permissionsStatus": "ready",
+ "hasPermissions": True
+ }
return ActionResult(
success=True,
documents=[ActionDocument(
documentName=f"outlook_permissions_check_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(result_data, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)]
)
else:
@@ -1711,12 +1796,19 @@ Return JSON:
"message": "Please re-authenticate your Microsoft connection to get updated permissions."
}
+ validationMetadata = {
+ "actionType": "outlook.checkPermissions",
+ "connectionReference": connectionReference,
+ "permissionsStatus": "needs_reauthentication",
+ "hasPermissions": False
+ }
return ActionResult(
success=False,
documents=[ActionDocument(
documentName=f"outlook_permissions_check_{self._format_timestamp_for_filename()}.json",
documentData=json.dumps(result_data, indent=2),
- mimeType="application/json"
+ mimeType="application/json",
+ validationMetadata=validationMetadata
)],
error="Connection lacks necessary permissions for Outlook operations"
)
diff --git a/modules/workflows/methods/methodSharepoint.py b/modules/workflows/methods/methodSharepoint.py
index 92d77e8e..a433e04c 100644
--- a/modules/workflows/methods/methodSharepoint.py
+++ b/modules/workflows/methods/methodSharepoint.py
@@ -1072,6 +1072,13 @@ class MethodSharepoint(MethodBase):
outputExtension = ".json" # Default
outputMimeType = "application/json" # Default
+ validationMetadata = {
+ "actionType": "sharepoint.findDocumentPath",
+ "searchQuery": searchQuery,
+ "maxResults": maxResults,
+ "totalResults": len(foundDocuments),
+ "hasResults": len(foundDocuments) > 0
+ }
return ActionResult(
success=True,
@@ -1079,7 +1086,8 @@ class MethodSharepoint(MethodBase):
ActionDocument(
documentName=f"sharepoint_find_path_{self._format_timestamp_for_filename()}{outputExtension}",
documentData=json.dumps(resultData, indent=2),
- mimeType=outputMimeType
+ mimeType=outputMimeType,
+ validationMetadata=validationMetadata
)
]
)
@@ -1336,19 +1344,40 @@ class MethodSharepoint(MethodBase):
if fileContent and isinstance(fileContent, bytes):
# Encode binary content as Base64 string
base64Content = base64.b64encode(fileContent).decode('utf-8')
+ validationMetadata = {
+ "actionType": "sharepoint.readDocuments",
+ "fileName": fileName,
+ "sharepointFileId": resultItem.get("sharepointFileId"),
+ "siteName": resultItem.get("siteName"),
+ "mimeType": mimeType,
+ "contentType": "binary",
+ "size": len(fileContent),
+ "includeMetadata": includeMetadata
+ }
actionDoc = ActionDocument(
documentName=fileName,
documentData=base64Content, # Base64 string for binary files
- mimeType=mimeType
+ mimeType=mimeType,
+ validationMetadata=validationMetadata
)
actionDocuments.append(actionDoc)
logger.info(f"Stored binary file {fileName} ({len(fileContent)} bytes) as Base64 in ActionDocument")
elif fileContent:
# Text content - store directly in documentData
+ validationMetadata = {
+ "actionType": "sharepoint.readDocuments",
+ "fileName": fileName,
+ "sharepointFileId": resultItem.get("sharepointFileId"),
+ "siteName": resultItem.get("siteName"),
+ "mimeType": mimeType,
+ "contentType": "text",
+ "includeMetadata": includeMetadata
+ }
actionDoc = ActionDocument(
documentName=fileName,
documentData=fileContent if isinstance(fileContent, str) else str(fileContent),
- mimeType=mimeType
+ mimeType=mimeType,
+ validationMetadata=validationMetadata
)
actionDocuments.append(actionDoc)
else:
@@ -1366,10 +1395,20 @@ class MethodSharepoint(MethodBase):
if resultItem.get("metadata"):
docData["metadata"] = resultItem["metadata"]
+ validationMetadata = {
+ "actionType": "sharepoint.readDocuments",
+ "fileName": fileName,
+ "sharepointFileId": resultItem.get("sharepointFileId"),
+ "siteName": resultItem.get("siteName"),
+ "mimeType": mimeType,
+ "contentType": "metadata_only",
+ "includeMetadata": includeMetadata
+ }
actionDoc = ActionDocument(
documentName=fileName,
documentData=json.dumps(docData, indent=2),
- mimeType=mimeType
+ mimeType=mimeType,
+ validationMetadata=validationMetadata
)
actionDocuments.append(actionDoc)
@@ -1583,6 +1622,13 @@ class MethodSharepoint(MethodBase):
outputExtension = ".json" # Default
outputMimeType = "application/json" # Default
+ validationMetadata = {
+ "actionType": "sharepoint.readDocuments",
+ "connectionReference": connectionReference,
+ "documentCount": len(readResults),
+ "includeMetadata": includeMetadata,
+ "sitesSearched": len(sites)
+ }
return ActionResult(
success=True,
@@ -1590,7 +1636,8 @@ class MethodSharepoint(MethodBase):
ActionDocument(
documentName=f"sharepoint_documents_{self._format_timestamp_for_filename()}{outputExtension}",
documentData=json.dumps(resultData, indent=2),
- mimeType=outputMimeType
+ mimeType=outputMimeType,
+ validationMetadata=validationMetadata
)
]
)
@@ -1998,6 +2045,15 @@ class MethodSharepoint(MethodBase):
outputExtension = ".json" # Default
outputMimeType = "application/json" # Default
+ validationMetadata = {
+ "actionType": "sharepoint.uploadDocument",
+ "connectionReference": connectionReference,
+ "uploadPath": uploadPath,
+ "fileNames": fileNames,
+ "uploadCount": len(uploadResults),
+ "successfulUploads": len([r for r in uploadResults if r.get("uploadStatus") == "success"]),
+ "failedUploads": len([r for r in uploadResults if r.get("uploadStatus") == "failed"])
+ }
return ActionResult(
success=True,
@@ -2005,7 +2061,8 @@ class MethodSharepoint(MethodBase):
ActionDocument(
documentName=f"sharepoint_upload_{self._format_timestamp_for_filename()}{outputExtension}",
documentData=json.dumps(resultData, indent=2),
- mimeType=outputMimeType
+ mimeType=outputMimeType,
+ validationMetadata=validationMetadata
)
]
)
@@ -2459,6 +2516,14 @@ class MethodSharepoint(MethodBase):
outputExtension = ".json" # Default
outputMimeType = "application/json" # Default
+ validationMetadata = {
+ "actionType": "sharepoint.listDocuments",
+ "pathQuery": listQuery,
+ "includeSubfolders": includeSubfolders,
+ "sitesSearched": len(sites),
+ "folderCount": len(listResults),
+ "totalItems": sum(len(result.get("siteResults", [])) for result in listResults)
+ }
return ActionResult(
success=True,
@@ -2466,7 +2531,8 @@ class MethodSharepoint(MethodBase):
ActionDocument(
documentName=f"sharepoint_document_list_{self._format_timestamp_for_filename()}{outputExtension}",
documentData=json.dumps(resultData, indent=2),
- mimeType=outputMimeType
+ mimeType=outputMimeType,
+ validationMetadata=validationMetadata
)
]
)