From 49f3660d89075490c7810847f9553442f93a41a4 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 29 Apr 2026 01:03:40 +0200
Subject: [PATCH] fixes infomaniak download
---
modules/aicore/aicorePluginAnthropic.py | 47 ++++++++++++++++---
.../providerInfomaniak/connectorInfomaniak.py | 13 ++++-
modules/datamodels/datamodelDataSource.py | 7 ++-
.../workspace/routeFeatureWorkspace.py | 3 ++
.../coreTools/_dataSourceTools.py | 8 ++++
5 files changed, 68 insertions(+), 10 deletions(-)
diff --git a/modules/aicore/aicorePluginAnthropic.py b/modules/aicore/aicorePluginAnthropic.py
index 1119f115..3b5eccda 100644
--- a/modules/aicore/aicorePluginAnthropic.py
+++ b/modules/aicore/aicorePluginAnthropic.py
@@ -13,6 +13,35 @@ from modules.datamodels.datamodelAi import AiModel, PriorityEnum, ProcessingMode
# Configure logger
logger = logging.getLogger(__name__)
+
+def _supportsCustomTemperature(modelName: str) -> bool:
+ """Check whether an Anthropic model accepts a custom ``temperature``.
+
+ Anthropic's Extended-Thinking models (Claude 4.7 Opus and the
+ upcoming 4.7 Sonnet/Haiku, plus all 5.x and beyond) reject every
+ ``temperature`` value with HTTP 400
+ ``{"error": "`temperature` is deprecated for this model."}`` --
+ only the model's internal default is accepted. Older Claude 4.5 /
+ 4.6 models still accept any value in [0, 1].
+
+ Returns:
+ True if ``temperature`` may be sent; False if it must be omitted.
+ """
+ if not modelName:
+ return True
+ name = modelName.lower()
+ if name.startswith("claude-opus-4-7"):
+ return False
+ if name.startswith("claude-sonnet-4-7"):
+ return False
+ if name.startswith("claude-haiku-4-7"):
+ return False
+ # 5.x and beyond: same Extended-Thinking family, no custom temperature.
+ if name.startswith("claude-opus-5") or name.startswith("claude-sonnet-5") or name.startswith("claude-haiku-5"):
+ return False
+ return True
+
+
def loadConfigData():
"""Load configuration data for Anthropic connector"""
return {
@@ -276,9 +305,12 @@ class AiAnthropic(BaseConnectorAi):
payload: Dict[str, Any] = {
"model": model.name,
"messages": converted_messages,
- "temperature": temperature,
}
-
+ # Extended-Thinking models (claude-opus-4-7 etc.) reject any
+ # `temperature` value -- only the model default is accepted.
+ if _supportsCustomTemperature(model.name):
+ payload["temperature"] = temperature
+
# Anthropic requires max_tokens - use provided value or throw error
if maxTokens is None:
raise ValueError("maxTokens must be provided for Anthropic API calls")
@@ -381,10 +413,11 @@ class AiAnthropic(BaseConnectorAi):
payload: Dict[str, Any] = {
"model": model.name,
"messages": converted,
- "temperature": temperature,
"max_tokens": model.maxTokens,
"stream": True,
}
+ if _supportsCustomTemperature(model.name):
+ payload["temperature"] = temperature
if system_prompt:
payload["system"] = system_prompt
if modelCall.tools:
@@ -608,10 +641,10 @@ class AiAnthropic(BaseConnectorAi):
if systemPrompt:
payload["system"] = systemPrompt
-
- # Set temperature from model
- payload["temperature"] = temperature
-
+
+ if _supportsCustomTemperature(model.name):
+ payload["temperature"] = temperature
+
# Make API call with headers from httpClient (which includes anthropic-version)
response = await self.httpClient.post(
"https://api.anthropic.com/v1/messages",
diff --git a/modules/connectors/providerInfomaniak/connectorInfomaniak.py b/modules/connectors/providerInfomaniak/connectorInfomaniak.py
index 9153ad73..cb592261 100644
--- a/modules/connectors/providerInfomaniak/connectorInfomaniak.py
+++ b/modules/connectors/providerInfomaniak/connectorInfomaniak.py
@@ -101,13 +101,22 @@ async def _infomaniakDownload(
endpoint: str,
baseUrl: str = _API_BASE,
) -> Optional[bytes]:
- """Binary download from an Infomaniak host. Returns bytes or ``None``."""
+ """Binary download from an Infomaniak host. Returns bytes or ``None``.
+
+ Unlike :func:`_infomaniakGet`, this follows redirects: kDrive's
+ ``/2/drive/{driveId}/files/{fileId}/download`` answers with
+ ``302 -> presigned CDN URL`` (standard for bandwidth-heavy
+ transfers), and the same pattern shows up on Calendar/Contacts
+ export endpoints. Refusing to follow would lose every download.
+ The Authorization header is preserved across the redirect by
+ aiohttp because the host is the same Infomaniak property.
+ """
url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}"
headers = {"Authorization": f"Bearer {token}"}
timeout = aiohttp.ClientTimeout(total=120)
try:
async with aiohttp.ClientSession(timeout=timeout) as session:
- async with session.get(url, headers=headers, allow_redirects=False) as resp:
+ async with session.get(url, headers=headers, allow_redirects=True) as resp:
if resp.status == 200:
return await resp.read()
logger.warning(
diff --git a/modules/datamodels/datamodelDataSource.py b/modules/datamodels/datamodelDataSource.py
index 10d2976c..d9e40bde 100644
--- a/modules/datamodels/datamodelDataSource.py
+++ b/modules/datamodels/datamodelDataSource.py
@@ -26,7 +26,12 @@ class DataSource(PowerOnModel):
json_schema_extra={"label": "Verbindungs-ID", "fk_target": {"db": "poweron_app", "table": "UserConnection", "labelField": "externalUsername"}},
)
sourceType: str = Field(
- description="sharepointFolder, googleDriveFolder, outlookFolder, ftpFolder, clickupList (path under /team/...)",
+ description=(
+ "sharepointFolder, onedriveFolder, googleDriveFolder, "
+ "outlookFolder, gmailFolder, ftpFolder, clickupList "
+ "(path under /team/...), kdriveFolder, calendarFolder, "
+ "contactFolder"
+ ),
json_schema_extra={"label": "Quellentyp"},
)
path: str = Field(
diff --git a/modules/features/workspace/routeFeatureWorkspace.py b/modules/features/workspace/routeFeatureWorkspace.py
index 3fa85c6c..3e1a54b7 100644
--- a/modules/features/workspace/routeFeatureWorkspace.py
+++ b/modules/features/workspace/routeFeatureWorkspace.py
@@ -188,6 +188,9 @@ _SOURCE_TYPE_TO_SERVICE = {
"gmailFolder": "gmail",
"ftpFolder": "files",
"clickupList": "clickup",
+ "kdriveFolder": "kdrive",
+ "calendarFolder": "calendar",
+ "contactFolder": "contact",
}
diff --git a/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py b/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py
index a369530b..abe24c95 100644
--- a/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py
+++ b/modules/serviceCenter/services/serviceAgent/coreTools/_dataSourceTools.py
@@ -37,6 +37,11 @@ def _registerDataSourceTools(registry: ToolRegistry, services):
return getattr(chatService, "interfaceDbComponent", None)
# ---- DataSource convenience tools ----
+ # Maps the FE-side `sourceType` literal (see SourcesTab.tsx
+ # `_SERVICE_TO_SOURCE_TYPE`) to the Connector's `service` key in
+ # `_SERVICE_MAP`. Keep this table in sync with both the FE and the
+ # Connector `_SERVICE_MAP` entries -- a missing row produces
+ # "Service '' not available" in the agent tools.
_SOURCE_TYPE_TO_SERVICE = {
"sharepointFolder": "sharepoint",
"onedriveFolder": "onedrive",
@@ -45,6 +50,9 @@ def _registerDataSourceTools(registry: ToolRegistry, services):
"gmailFolder": "gmail",
"ftpFolder": "files",
"clickupList": "clickup",
+ "kdriveFolder": "kdrive",
+ "calendarFolder": "calendar",
+ "contactFolder": "contact",
}
async def _resolveDataSource(dsId: str):