diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py
index 42c88dda8b..5b871f69f9 100644
--- a/api/configs/feature/__init__.py
+++ b/api/configs/feature/__init__.py
@@ -362,11 +362,11 @@ class HttpConfig(BaseSettings):
)
HTTP_REQUEST_MAX_READ_TIMEOUT: int = Field(
- ge=1, description="Maximum read timeout in seconds for HTTP requests", default=60
+ ge=1, description="Maximum read timeout in seconds for HTTP requests", default=600
)
HTTP_REQUEST_MAX_WRITE_TIMEOUT: int = Field(
- ge=1, description="Maximum write timeout in seconds for HTTP requests", default=20
+ ge=1, description="Maximum write timeout in seconds for HTTP requests", default=600
)
HTTP_REQUEST_NODE_MAX_BINARY_SIZE: PositiveInt = Field(
@@ -771,7 +771,7 @@ class MailConfig(BaseSettings):
MAIL_TEMPLATING_TIMEOUT: int = Field(
description="""
- Timeout for email templating in seconds. Used to prevent infinite loops in malicious templates.
+ Timeout for email templating in seconds. Used to prevent infinite loops in malicious templates.
Only available in sandbox mode.""",
default=3,
)
diff --git a/api/core/plugin/utils/chunk_merger.py b/api/core/plugin/utils/chunk_merger.py
index e30076f9d3..28cb70f96a 100644
--- a/api/core/plugin/utils/chunk_merger.py
+++ b/api/core/plugin/utils/chunk_merger.py
@@ -1,6 +1,6 @@
from collections.abc import Generator
from dataclasses import dataclass, field
-from typing import TypeVar, Union, cast
+from typing import TypeVar, Union
from core.agent.entities import AgentInvokeMessage
from core.tools.entities.tool_entities import ToolInvokeMessage
@@ -87,7 +87,8 @@ def merge_blob_chunks(
),
meta=resp.meta,
)
- yield cast(MessageType, merged_message)
+ assert isinstance(merged_message, (ToolInvokeMessage, AgentInvokeMessage))
+ yield merged_message # type: ignore
# Clean up the buffer
del files[chunk_id]
else:
diff --git a/api/core/workflow/nodes/knowledge_index/knowledge_index_node.py b/api/core/workflow/nodes/knowledge_index/knowledge_index_node.py
index 8d685fa82e..05e0c7707a 100644
--- a/api/core/workflow/nodes/knowledge_index/knowledge_index_node.py
+++ b/api/core/workflow/nodes/knowledge_index/knowledge_index_node.py
@@ -2,7 +2,7 @@ import datetime
import logging
import time
from collections.abc import Mapping
-from typing import Any, cast
+from typing import Any
from sqlalchemy import func, select
@@ -62,7 +62,7 @@ class KnowledgeIndexNode(Node):
return self._node_data
def _run(self) -> NodeRunResult: # type: ignore
- node_data = cast(KnowledgeIndexNodeData, self._node_data)
+ node_data = self._node_data
variable_pool = self.graph_runtime_state.variable_pool
dataset_id = variable_pool.get(["sys", SystemVariableKey.DATASET_ID])
if not dataset_id:
diff --git a/api/pyrightconfig.json b/api/pyrightconfig.json
index 9bc7498cb2..67571316a9 100644
--- a/api/pyrightconfig.json
+++ b/api/pyrightconfig.json
@@ -25,7 +25,6 @@
"reportMissingParameterType": "hint",
"reportMissingTypeArgument": "hint",
"reportUnnecessaryComparison": "hint",
- "reportUnnecessaryCast": "hint",
"reportUnnecessaryIsInstance": "hint",
"reportUntypedFunctionDecorator": "hint",
diff --git a/api/services/tools/mcp_tools_manage_service.py b/api/services/tools/mcp_tools_manage_service.py
index dd626dd615..605ad8379b 100644
--- a/api/services/tools/mcp_tools_manage_service.py
+++ b/api/services/tools/mcp_tools_manage_service.py
@@ -1,7 +1,7 @@
import hashlib
import json
from datetime import datetime
-from typing import Any, cast
+from typing import Any
from sqlalchemy import or_
from sqlalchemy.exc import IntegrityError
@@ -55,7 +55,7 @@ class MCPToolManageService:
cache=NoOpProviderCredentialCache(),
)
- return cast(dict[str, str], encrypter_instance.encrypt(headers))
+ return encrypter_instance.encrypt(headers)
@staticmethod
def get_mcp_provider_by_provider_id(provider_id: str, tenant_id: str) -> MCPToolProvider:
diff --git a/api/tests/unit_tests/configs/test_dify_config.py b/api/tests/unit_tests/configs/test_dify_config.py
index 2968de4b91..209b6bf59b 100644
--- a/api/tests/unit_tests/configs/test_dify_config.py
+++ b/api/tests/unit_tests/configs/test_dify_config.py
@@ -15,13 +15,13 @@ def test_dify_config(monkeypatch: pytest.MonkeyPatch):
# Set environment variables using monkeypatch
monkeypatch.setenv("CONSOLE_API_URL", "https://example.com")
monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com")
- monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30")
+ monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30") # Custom value for testing
monkeypatch.setenv("DB_USERNAME", "postgres")
monkeypatch.setenv("DB_PASSWORD", "postgres")
monkeypatch.setenv("DB_HOST", "localhost")
monkeypatch.setenv("DB_PORT", "5432")
monkeypatch.setenv("DB_DATABASE", "dify")
- monkeypatch.setenv("HTTP_REQUEST_MAX_READ_TIMEOUT", "600")
+ monkeypatch.setenv("HTTP_REQUEST_MAX_READ_TIMEOUT", "300") # Custom value for testing
# load dotenv file with pydantic-settings
config = DifyConfig()
@@ -35,16 +35,36 @@ def test_dify_config(monkeypatch: pytest.MonkeyPatch):
assert config.SENTRY_TRACES_SAMPLE_RATE == 1.0
assert config.TEMPLATE_TRANSFORM_MAX_LENGTH == 400_000
- # annotated field with default value
- assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 600
+ # annotated field with custom configured value
+ assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 300
- # annotated field with configured value
+ # annotated field with custom configured value
assert config.HTTP_REQUEST_MAX_WRITE_TIMEOUT == 30
# values from pyproject.toml
assert Version(config.project.version) >= Version("1.0.0")
+def test_http_timeout_defaults(monkeypatch: pytest.MonkeyPatch):
+ """Test that HTTP timeout defaults are correctly set"""
+ # clear system environment variables
+ os.environ.clear()
+
+ # Set minimal required env vars
+ monkeypatch.setenv("DB_USERNAME", "postgres")
+ monkeypatch.setenv("DB_PASSWORD", "postgres")
+ monkeypatch.setenv("DB_HOST", "localhost")
+ monkeypatch.setenv("DB_PORT", "5432")
+ monkeypatch.setenv("DB_DATABASE", "dify")
+
+ config = DifyConfig()
+
+ # Verify default timeout values
+ assert config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT == 10
+ assert config.HTTP_REQUEST_MAX_READ_TIMEOUT == 600
+ assert config.HTTP_REQUEST_MAX_WRITE_TIMEOUT == 600
+
+
# NOTE: If there is a `.env` file in your Workspace, this test might not succeed as expected.
# This is due to `pymilvus` loading all the variables from the `.env` file into `os.environ`.
def test_flask_configs(monkeypatch: pytest.MonkeyPatch):
@@ -55,7 +75,6 @@ def test_flask_configs(monkeypatch: pytest.MonkeyPatch):
# Set environment variables using monkeypatch
monkeypatch.setenv("CONSOLE_API_URL", "https://example.com")
monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com")
- monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30")
monkeypatch.setenv("DB_USERNAME", "postgres")
monkeypatch.setenv("DB_PASSWORD", "postgres")
monkeypatch.setenv("DB_HOST", "localhost")
@@ -105,7 +124,6 @@ def test_inner_api_config_exist(monkeypatch: pytest.MonkeyPatch):
# Set environment variables using monkeypatch
monkeypatch.setenv("CONSOLE_API_URL", "https://example.com")
monkeypatch.setenv("CONSOLE_WEB_URL", "https://example.com")
- monkeypatch.setenv("HTTP_REQUEST_MAX_WRITE_TIMEOUT", "30")
monkeypatch.setenv("DB_USERNAME", "postgres")
monkeypatch.setenv("DB_PASSWORD", "postgres")
monkeypatch.setenv("DB_HOST", "localhost")
diff --git a/docker/.env.example b/docker/.env.example
index e04ef9e5bc..6d07cf7fa5 100644
--- a/docker/.env.example
+++ b/docker/.env.example
@@ -930,6 +930,16 @@ WORKFLOW_LOG_CLEANUP_BATCH_SIZE=100
HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760
HTTP_REQUEST_NODE_MAX_TEXT_SIZE=1048576
HTTP_REQUEST_NODE_SSL_VERIFY=True
+
+# HTTP request node timeout configuration
+# Maximum timeout values (in seconds) that users can set in HTTP request nodes
+# - Connect timeout: Time to wait for establishing connection (default: 10s)
+# - Read timeout: Time to wait for receiving response data (default: 600s, 10 minutes)
+# - Write timeout: Time to wait for sending request data (default: 600s, 10 minutes)
+HTTP_REQUEST_MAX_CONNECT_TIMEOUT=10
+HTTP_REQUEST_MAX_READ_TIMEOUT=600
+HTTP_REQUEST_MAX_WRITE_TIMEOUT=600
+
# Base64 encoded CA certificate data for custom certificate verification (PEM format, optional)
# HTTP_REQUEST_NODE_SSL_CERT_DATA=LS0tLS1CRUdJTi...
# Base64 encoded client certificate data for mutual TLS authentication (PEM format, optional)
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index abac6d3b1e..5d47471093 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -418,6 +418,9 @@ x-shared-env: &shared-api-worker-env
HTTP_REQUEST_NODE_MAX_BINARY_SIZE: ${HTTP_REQUEST_NODE_MAX_BINARY_SIZE:-10485760}
HTTP_REQUEST_NODE_MAX_TEXT_SIZE: ${HTTP_REQUEST_NODE_MAX_TEXT_SIZE:-1048576}
HTTP_REQUEST_NODE_SSL_VERIFY: ${HTTP_REQUEST_NODE_SSL_VERIFY:-True}
+ HTTP_REQUEST_MAX_CONNECT_TIMEOUT: ${HTTP_REQUEST_MAX_CONNECT_TIMEOUT:-10}
+ HTTP_REQUEST_MAX_READ_TIMEOUT: ${HTTP_REQUEST_MAX_READ_TIMEOUT:-600}
+ HTTP_REQUEST_MAX_WRITE_TIMEOUT: ${HTTP_REQUEST_MAX_WRITE_TIMEOUT:-600}
RESPECT_XFORWARD_HEADERS_ENABLED: ${RESPECT_XFORWARD_HEADERS_ENABLED:-false}
SSRF_PROXY_HTTP_URL: ${SSRF_PROXY_HTTP_URL:-http://ssrf_proxy:3128}
SSRF_PROXY_HTTPS_URL: ${SSRF_PROXY_HTTPS_URL:-http://ssrf_proxy:3128}
diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
index 7c259f1a78..bdaeacb5c0 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx
@@ -276,7 +276,7 @@ function Form<
-
{label[language] || label.en_US}
+
{label[language] || label.en_US}
{required && (
*
)}
diff --git a/web/app/components/share/text-generation/result/index.tsx b/web/app/components/share/text-generation/result/index.tsx
index ddc0d772c3..7a4e606636 100644
--- a/web/app/components/share/text-generation/result/index.tsx
+++ b/web/app/components/share/text-generation/result/index.tsx
@@ -78,15 +78,15 @@ const Result: FC
= ({
setRespondingFalse()
}, [controlStopResponding])
- const [completionRes, doSetCompletionRes] = useState('')
- const completionResRef = useRef()
- const setCompletionRes = (res: any) => {
+ const [completionRes, doSetCompletionRes] = useState('')
+ const completionResRef = useRef('')
+ const setCompletionRes = (res: string) => {
completionResRef.current = res
doSetCompletionRes(res)
}
const getCompletionRes = () => completionResRef.current
const [workflowProcessData, doSetWorkflowProcessData] = useState()
- const workflowProcessDataRef = useRef()
+ const workflowProcessDataRef = useRef(undefined)
const setWorkflowProcessData = (data: WorkflowProcess) => {
workflowProcessDataRef.current = data
doSetWorkflowProcessData(data)
diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts
index c1d17b48ef..8e85a5f9b0 100644
--- a/web/app/components/tools/utils/to-form-schema.ts
+++ b/web/app/components/tools/utils/to-form-schema.ts
@@ -45,6 +45,7 @@ export const toolCredentialToFormSchemas = (parameters: ToolCredential[]) => {
return {
...parameter,
variable: parameter.name,
+ type: toType(parameter.type),
label: parameter.label,
tooltip: parameter.help,
show_on: [],
diff --git a/web/app/components/workflow/hooks/use-workflow-history.ts b/web/app/components/workflow/hooks/use-workflow-history.ts
index a9b2f0f699..58bbe415a8 100644
--- a/web/app/components/workflow/hooks/use-workflow-history.ts
+++ b/web/app/components/workflow/hooks/use-workflow-history.ts
@@ -41,16 +41,16 @@ export const useWorkflowHistory = () => {
const { store: workflowHistoryStore } = useWorkflowHistoryStore()
const { t } = useTranslation()
- const [undoCallbacks, setUndoCallbacks] = useState([])
- const [redoCallbacks, setRedoCallbacks] = useState([])
+ const [undoCallbacks, setUndoCallbacks] = useState<(() => void)[]>([])
+ const [redoCallbacks, setRedoCallbacks] = useState<(() => void)[]>([])
- const onUndo = useCallback((callback: unknown) => {
- setUndoCallbacks((prev: any) => [...prev, callback])
+ const onUndo = useCallback((callback: () => void) => {
+ setUndoCallbacks(prev => [...prev, callback])
return () => setUndoCallbacks(prev => prev.filter(cb => cb !== callback))
}, [])
- const onRedo = useCallback((callback: unknown) => {
- setRedoCallbacks((prev: any) => [...prev, callback])
+ const onRedo = useCallback((callback: () => void) => {
+ setRedoCallbacks(prev => [...prev, callback])
return () => setRedoCallbacks(prev => prev.filter(cb => cb !== callback))
}, [])
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
index ba27d023e7..4d74e09fde 100644
--- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
+++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
@@ -127,7 +127,7 @@ const VarReferencePicker: FC = ({
const reactflow = useReactFlow()
- const startNode = availableNodes.find((node: any) => {
+ const startNode = availableNodes.find((node: Node) => {
return node.data.type === BlockEnum.Start
})
diff --git a/web/app/components/workflow/nodes/http/components/timeout/index.tsx b/web/app/components/workflow/nodes/http/components/timeout/index.tsx
index 40ebab0e2a..bb84091d67 100644
--- a/web/app/components/workflow/nodes/http/components/timeout/index.tsx
+++ b/web/app/components/workflow/nodes/http/components/timeout/index.tsx
@@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next'
import type { Timeout as TimeoutPayloadType } from '../../types'
import Input from '@/app/components/base/input'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
+import { useStore } from '@/app/components/workflow/store'
+import { BlockEnum } from '@/app/components/workflow/types'
type Props = {
readonly: boolean
@@ -61,6 +63,11 @@ const Timeout: FC = ({ readonly, payload, onChange }) => {
const { t } = useTranslation()
const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {}
+ // Get default config from store for max timeout values
+ const nodesDefaultConfigs = useStore(s => s.nodesDefaultConfigs)
+ const defaultConfig = nodesDefaultConfigs?.[BlockEnum.HttpRequest]
+ const defaultTimeout = defaultConfig?.timeout || {}
+
return (
@@ -73,7 +80,7 @@ const Timeout: FC
= ({ readonly, payload, onChange }) => {
value={connect}
onChange={v => onChange?.({ ...payload, connect: v })}
min={1}
- max={max_connect_timeout || 300}
+ max={max_connect_timeout || defaultTimeout.max_connect_timeout || 10}
/>
= ({ readonly, payload, onChange }) => {
value={read}
onChange={v => onChange?.({ ...payload, read: v })}
min={1}
- max={max_read_timeout || 600}
+ max={max_read_timeout || defaultTimeout.max_read_timeout || 600}
/>
= ({ readonly, payload, onChange }) => {
value={write}
onChange={v => onChange?.({ ...payload, write: v })}
min={1}
- max={max_write_timeout || 600}
+ max={max_write_timeout || defaultTimeout.max_write_timeout || 600}
/>
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx
index 4adb924190..be80a8aac7 100644
--- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx
+++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx
@@ -120,7 +120,7 @@ const JsonSchemaConfig: FC
= ({
setJson(JSON.stringify(schema, null, 2))
}, [currentTab])
- const handleSubmit = useCallback((schema: any) => {
+ const handleSubmit = useCallback((schema: Record) => {
const jsonSchema = jsonToSchema(schema) as SchemaRoot
if (currentTab === SchemaView.VisualEditor)
setJsonSchema(jsonSchema)
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx
index 1db31cd470..4aa0f99d3f 100644
--- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx
+++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/index.tsx
@@ -152,14 +152,16 @@ const EditCard: FC = ({
}, [isAdvancedEditing, emitPropertyOptionsChange, currentFields])
const handleAdvancedOptionsChange = useCallback((options: AdvancedOptionsType) => {
- let enumValue: any = options.enum
- if (enumValue === '') {
+ let enumValue: SchemaEnumType | undefined
+ if (options.enum === '') {
enumValue = undefined
}
else {
- enumValue = options.enum.replace(/\s/g, '').split(',')
+ const stringArray = options.enum.replace(/\s/g, '').split(',')
if (currentFields.type === Type.number)
- enumValue = (enumValue as SchemaEnumType).map(value => Number(value)).filter(num => !Number.isNaN(num))
+ enumValue = stringArray.map(value => Number(value)).filter(num => !Number.isNaN(num))
+ else
+ enumValue = stringArray
}
setCurrentFields(prev => ({ ...prev, enum: enumValue }))
if (isAdvancedEditing) return
diff --git a/web/service/base.ts b/web/service/base.ts
index 6e189f1481..358f54183b 100644
--- a/web/service/base.ts
+++ b/web/service/base.ts
@@ -180,7 +180,7 @@ const handleStream = (
let isFirstMessage = true
function read() {
let hasError = false
- reader?.read().then((result: any) => {
+ reader?.read().then((result: ReadableStreamReadResult) => {
if (result.done) {
onCompleted?.()
return
@@ -322,7 +322,21 @@ const handleStream = (
const baseFetch = base
-export const upload = async (options: any, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise => {
+type UploadOptions = {
+ xhr: XMLHttpRequest
+ method: string
+ url?: string
+ headers?: Record
+ data: FormData
+ onprogress?: (this: XMLHttpRequest, ev: ProgressEvent) => void
+}
+
+type UploadResponse = {
+ id: string
+ [key: string]: unknown
+}
+
+export const upload = async (options: UploadOptions, isPublicAPI?: boolean, url?: string, searchParams?: string): Promise => {
const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
const token = await getAccessToken(isPublicAPI)
const defaultOptions = {
@@ -331,18 +345,18 @@ export const upload = async (options: any, isPublicAPI?: boolean, url?: string,
headers: {
Authorization: `Bearer ${token}`,
},
- data: {},
}
- options = {
+ const mergedOptions = {
...defaultOptions,
...options,
- headers: { ...defaultOptions.headers, ...options.headers },
+ url: options.url || defaultOptions.url,
+ headers: { ...defaultOptions.headers, ...options.headers } as Record,
}
return new Promise((resolve, reject) => {
- const xhr = options.xhr
- xhr.open(options.method, options.url)
- for (const key in options.headers)
- xhr.setRequestHeader(key, options.headers[key])
+ const xhr = mergedOptions.xhr
+ xhr.open(mergedOptions.method, mergedOptions.url)
+ for (const key in mergedOptions.headers)
+ xhr.setRequestHeader(key, mergedOptions.headers[key])
xhr.withCredentials = true
xhr.responseType = 'json'
@@ -354,8 +368,9 @@ export const upload = async (options: any, isPublicAPI?: boolean, url?: string,
reject(xhr)
}
}
- xhr.upload.onprogress = options.onprogress
- xhr.send(options.data)
+ if (mergedOptions.onprogress)
+ xhr.upload.onprogress = mergedOptions.onprogress
+ xhr.send(mergedOptions.data)
})
}
@@ -432,7 +447,7 @@ export const ssePost = async (
if (!/^[23]\d{2}$/.test(String(res.status))) {
if (res.status === 401) {
if (isPublicAPI) {
- res.json().then((data: any) => {
+ res.json().then((data: { code?: string; message?: string }) => {
if (isPublicAPI) {
if (data.code === 'web_app_access_denied')
requiredWebSSOLogin(data.message, 403)