feat: 添加MinIO文件系统支持并优化外部连接器功能

- 添加MinIO用户文件系统配置选项(BEAVER_MINIO_ROOT_USER等)
- 更新外部连接器配置结构,包括BASE_URL和认证令牌设置
- 改进connector provider支持更多类型(official, feishu_bot等)
- 实现Mistral模型推理模式支持reasoning_effort参数
- 增强外部连接器策略配置和运行时配置管理
- 添加connector bridge事件验证和安全保护机制
- 优化任务路由逻辑,区分simple_chat和new_task场景
- 更新初始技能工具提示配置,分离authoring admin功能
This commit is contained in:
2026-06-05 11:46:40 +08:00
parent 236ac19789
commit 2c5205b06e
120 changed files with 8321 additions and 1865 deletions

View File

@ -431,6 +431,37 @@ def _connection_response_view(connection: Any) -> dict[str, Any]:
return view
def _connector_session_response_view(view: dict[str, Any]) -> dict[str, Any]:
result = dict(view)
metadata = result.get("metadata")
if isinstance(metadata, dict):
result["metadata"] = {
str(key): value
for key, value in metadata.items()
if not _is_sensitive_metadata_key(str(key))
}
return result
def _is_sensitive_metadata_key(key: str) -> bool:
lowered = key.lower()
return any(token in lowered for token in ("secret", "token", "password", "authorization", "credential"))
async def _activate_connected_channel(
request: Request,
registry: ChannelConnectorRegistry,
connection: Any,
) -> Any:
if connection.status != "connected":
return connection
runtime = get_channel_runtime(request)
config = (await registry.materialize_channel_configs()).get(connection.channel_id)
if config is not None:
await runtime.add_channel(connection.channel_id, config)
return registry.connection_store.get(connection.connection_id)
def _normalize_connection_config(config: dict[str, Any] | None) -> dict[str, Any]:
if not isinstance(config, dict):
return {}
@ -441,6 +472,36 @@ def _normalize_connection_config(config: dict[str, Any] | None) -> dict[str, Any
}
def _connector_bridge_guard(connection: Any, payload: WebConnectorBridgeEventRequest) -> None:
if connection.status == "revoked":
raise HTTPException(status_code=404, detail="Channel connection not found")
if connection.status not in {"connected", "running"}:
raise HTTPException(status_code=409, detail="Channel connection is not connected")
mismatches: list[str] = []
if payload.channel_id != connection.channel_id:
mismatches.append("channelId")
if payload.kind != connection.kind:
mismatches.append("kind")
if payload.account_id != connection.account_id:
mismatches.append("accountId")
if mismatches:
raise HTTPException(status_code=403, detail=f"Bridge event does not match connection: {', '.join(mismatches)}")
content = payload.content.strip()
if not content:
raise HTTPException(status_code=400, detail="Bridge event content is required")
max_chars = _positive_int(connection.runtime_config.get("maxMessageChars"), default=20000)
if len(content) > max_chars:
raise HTTPException(status_code=413, detail=f"Bridge event content exceeds maxMessageChars ({max_chars})")
def _positive_int(value: Any, *, default: int) -> int:
try:
number = int(value)
except (TypeError, ValueError):
return default
return number if number > 0 else default
def _camel_to_snake_text(value: str) -> str:
result: list[str] = []
for char in value.strip():
@ -721,8 +782,10 @@ def create_app(
connection_id = _clean_text(view.get("connectionId"))
connection_view = None
if connection_id:
connection_view = _connection_response_view(registry.connection_store.get(connection_id))
return WebConnectorSessionResponse(session=view, connection=connection_view)
connection = registry.connection_store.get(connection_id)
connection = await _activate_connected_channel(request, registry, connection)
connection_view = _connection_response_view(connection)
return WebConnectorSessionResponse(session=_connector_session_response_view(view), connection=connection_view)
@app.get("/api/channel-connector-sessions/{session_id}", response_model=WebConnectorSessionResponse)
async def get_channel_connector_session(session_id: str, request: Request) -> WebConnectorSessionResponse:
@ -739,11 +802,11 @@ def create_app(
raise HTTPException(status_code=400, detail="Connector does not support sessions")
view = await poll_session(session_id)
connection = registry.connection_store.get(connection.connection_id)
if connection.status == "connected":
runtime = get_channel_runtime(request)
config = (await registry.materialize_channel_configs())[connection.channel_id]
await runtime.add_channel(connection.channel_id, config)
return WebConnectorSessionResponse(session=view, connection=_connection_response_view(connection))
connection = await _activate_connected_channel(request, registry, connection)
return WebConnectorSessionResponse(
session=_connector_session_response_view(view),
connection=_connection_response_view(connection),
)
@app.post("/api/channel-connector-bridge/events", response_model=WebConnectorBridgeEventResponse)
async def accept_connector_bridge_event(
@ -760,8 +823,7 @@ def create_app(
connection = registry.connection_store.get(payload.connection_id)
except KeyError:
raise HTTPException(status_code=404, detail="Channel connection not found")
if connection.status == "revoked":
raise HTTPException(status_code=404, detail="Channel connection not found")
_connector_bridge_guard(connection, payload)
store = _message_dedupe_store(_channel_connection_workspace(request))
begin = store.begin(