refactor: 收口 V3 分层架构与插件兼容边界

This commit is contained in:
jxxghp
2026-08-18 13:22:02 +08:00
parent cca99bd421
commit 8472bcff43
274 changed files with 10730 additions and 6130 deletions
+72 -71
View File
@@ -16,7 +16,6 @@ from typing import Any, AsyncIterator, Callable, Optional, Union
from fastapi import Depends, File, Form, HTTPException, Request, UploadFile, status
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import FileResponse, StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.schemas.agent import AgentChatDisplaySaveRequest as _SchemaAgentChatDisplaySaveRequest
from app.schemas.agent import AgentChatSessionDetail as _SchemaAgentChatSessionDetail
@@ -45,12 +44,14 @@ from app.chain.message import MessageChain
from app.command import Command
from app.runtime.config import global_vars, settings
from app.runtime.events import Event, EventManager
from app.db import get_async_db
from app.db.oper.agentchat import AgentChatOper
from app.db.models import User
from app.db.models.agentchat import AgentChat
from app.db.oper.user import UserOper
from app.api.deps import get_current_active_user
from app.api.principal import ApiPrincipal
from app.api.deps import get_agent_chat_service, get_current_active_user
from app.application.messaging.chat import (
AgentChatRecord,
AgentChatService,
get_configured_agent_chat_service,
)
from app.application.security.user import get_configured_user_id_lookup
from app.application.messaging.agent import attach_web_agent_edit_queue, detach_web_agent_edit_queue
from app.application.messaging.agent import agent_interaction_manager
from app.application.messaging.agent import (
@@ -187,7 +188,7 @@ class _WebAgentEventPublisher:
self._pending_signal.clear()
def _ensure_superuser(user: User) -> None:
def _ensure_superuser(user: ApiPrincipal) -> None:
"""校验当前用户是否为超级管理员。"""
if not getattr(user, "is_superuser", False):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
@@ -199,7 +200,7 @@ def _ensure_superuser(user: User) -> None:
response_model=_SchemaResponse[_SchemaAgentMcpServerListData],
)
async def list_agent_mcp_servers(
current_user: User = Depends(get_current_active_user),
current_user: ApiPrincipal = Depends(get_current_active_user),
) -> _SchemaResponse:
"""
查询 Agent 外部 MCP 服务器配置。
@@ -224,7 +225,7 @@ async def list_agent_mcp_servers(
)
async def save_agent_mcp_servers(
request: _SchemaAgentMcpServersSaveRequest,
current_user: User = Depends(get_current_active_user),
current_user: ApiPrincipal = Depends(get_current_active_user),
) -> _SchemaResponse:
"""
保存 Agent 外部 MCP 服务器配置。
@@ -244,7 +245,7 @@ async def save_agent_mcp_servers(
)
async def test_agent_mcp_server(
request: _SchemaAgentMcpServerTestRequest,
current_user: User = Depends(get_current_active_user),
current_user: ApiPrincipal = Depends(get_current_active_user),
) -> _SchemaResponse:
"""
测试 Agent 外部 MCP 服务器连接并读取工具列表。
@@ -431,7 +432,7 @@ class _WebAgentMoviePilotAgentMixin:
if not self.user_id:
return False
try:
user = await UserOper().async_get_by_id(int(self.user_id))
user = get_configured_user_id_lookup()(int(self.user_id))
except (TypeError, ValueError):
return False
except Exception as e:
@@ -486,7 +487,7 @@ def _get_web_agent_type() -> type:
return _WEB_AGENT_TYPE
def _build_web_agent_session_id(user: User, session_id: Optional[str]) -> str:
def _build_web_agent_session_id(user: ApiPrincipal, session_id: Optional[str]) -> str:
"""
构建前端 Agent 会话 ID。
@@ -498,8 +499,8 @@ def _build_web_agent_session_id(user: User, session_id: Optional[str]) -> str:
if seed.startswith(WEB_AGENT_SESSION_PREFIX):
return seed
try:
existing_chat = AgentChatOper().get(session_id=seed)
if existing_chat and _can_access_agent_chat(existing_chat, user):
existing_chat = get_configured_agent_chat_service().get_sync(seed)
if existing_chat and AgentChatService.can_access(existing_chat, user):
return seed
except Exception as e:
logger.debug(f"读取WebAgent历史会话失败: {e}")
@@ -508,7 +509,7 @@ def _build_web_agent_session_id(user: User, session_id: Optional[str]) -> str:
return f"{WEB_AGENT_SESSION_PREFIX}{digest[:32]}"
def _can_access_agent_chat(chat: AgentChat, user: User) -> bool:
def _can_access_agent_chat(chat: Any, user: ApiPrincipal) -> bool:
"""
判断当前登录用户是否可以访问指定 Agent 会话。
@@ -524,15 +525,14 @@ def _can_access_agent_chat(chat: AgentChat, user: User) -> bool:
async def _get_accessible_agent_chat(
oper: AgentChatOper, session_id: str, user: User
) -> Optional[AgentChat]:
service: AgentChatService,
session_id: str,
user: ApiPrincipal,
) -> Optional[AgentChatRecord]:
"""
读取当前用户可访问的 Agent 会话。
"""
chat = await oper.async_get(session_id=session_id)
if not chat or not _can_access_agent_chat(chat, user):
return None
return chat
return await service.get_accessible(session_id, user)
def _append_web_agent_text_segment(assistant_message: dict, content: str) -> None:
@@ -631,7 +631,7 @@ def _apply_web_agent_display_event(event: dict, assistant_message: dict) -> None
def _save_web_agent_display_snapshot(
*,
session_id: str,
current_user: User,
current_user: ApiPrincipal,
messages: list[dict],
client_session_id: Optional[str] = None,
) -> None:
@@ -639,9 +639,9 @@ def _save_web_agent_display_snapshot(
保存 WebAgent 当前展示消息快照。
"""
try:
oper = AgentChatOper()
existing_chat = oper.get(session_id=session_id)
AgentChatOper().save_display_messages(
service = get_configured_agent_chat_service()
existing_chat = service.get_sync(session_id)
service.save_display_sync(
session_id=session_id,
user_id=(existing_chat.user_id if existing_chat else str(current_user.id)),
username=(existing_chat.username if existing_chat else current_user.name),
@@ -716,7 +716,7 @@ def _sanitize_web_agent_upload_name(
return safe_name
def _get_web_agent_upload_dir(user: User, session_id: Optional[str]) -> Path:
def _get_web_agent_upload_dir(user: ApiPrincipal, session_id: Optional[str]) -> Path:
"""
计算当前 Web Agent 会话的临时附件目录。
@@ -1425,7 +1425,7 @@ def _get_web_agent_unknown_command_message(text: str) -> Optional[str]:
return f"命令不存在:{command}"
def _ensure_web_agent_command_allowed(current_user: User) -> Optional[str]:
def _ensure_web_agent_command_allowed(current_user: ApiPrincipal) -> Optional[str]:
"""
校验当前 Web 用户是否可以执行传统斜杠命令。
@@ -1440,7 +1440,7 @@ def _ensure_web_agent_command_allowed(current_user: User) -> Optional[str]:
async def _collect_web_agent_traditional_events(
*,
text: str,
current_user: User,
current_user: ApiPrincipal,
original_message_id: Optional[Union[str, int]] = None,
original_chat_id: Optional[Union[str, int]] = None,
) -> list[dict]:
@@ -1637,7 +1637,7 @@ async def download_web_agent_file(file_id: str) -> FileResponse:
async def upload_web_agent_file(
file: UploadFile = File(...),
session_id: Optional[str] = Form(None),
current_user: User = Depends(get_current_active_user),
current_user: ApiPrincipal = Depends(get_current_active_user),
) -> _SchemaResponse:
"""
上传 Web 智能助手对话附件。
@@ -1680,7 +1680,7 @@ async def upload_web_agent_file(
)
async def web_agent_callback(
payload: _SchemaAgentWebChoiceRequest,
current_user: User = Depends(get_current_active_user),
current_user: ApiPrincipal = Depends(get_current_active_user),
) -> _SchemaResponse:
"""
接收 Web 智能助手选择卡片回调。
@@ -1717,7 +1717,7 @@ async def web_agent_callback(
response_model=_SchemaResponse[list[_SchemaAgentWebCommandInfo]],
)
async def list_web_agent_commands(
current_user: User = Depends(get_current_active_user),
current_user: ApiPrincipal = Depends(get_current_active_user),
) -> _SchemaResponse:
"""
获取当前 Web 智能助手可补全的斜杠命令。
@@ -1737,8 +1737,8 @@ async def list_web_agent_commands(
response_model=_SchemaResponse[list[_SchemaAgentChatSessionSummary]],
)
async def list_agent_chat_sessions(
current_user: User = Depends(get_current_active_user),
db: AsyncSession = Depends(get_async_db),
current_user: ApiPrincipal = Depends(get_current_active_user),
service: AgentChatService = Depends(get_agent_chat_service),
page: Optional[int] = 1,
count: Optional[int] = 30,
) -> _SchemaResponse:
@@ -1746,23 +1746,17 @@ async def list_agent_chat_sessions(
获取当前用户可访问的 Agent 历史会话列表。
:param current_user: 当前登录用户
:param db: 异步数据库会话
:param service: Agent 会话应用服务
:param page: 页码
:param count: 每页数量
:return: 会话摘要列表
"""
user_id = None if current_user.is_superuser else str(current_user.id)
username = None if current_user.is_superuser else current_user.name
chats = await AgentChatOper(db).async_list_by_page(
chats = await service.list(
current_user,
page=page,
count=count,
user_id=user_id,
username=username,
)
return _SchemaResponse(
success=True,
data=[AgentChatOper.to_summary(chat) for chat in chats],
)
return _SchemaResponse(success=True, data=chats)
@router.get(
@@ -1772,24 +1766,27 @@ async def list_agent_chat_sessions(
)
async def get_agent_chat_session(
session_id: str,
current_user: User = Depends(get_current_active_user),
db: AsyncSession = Depends(get_async_db),
current_user: ApiPrincipal = Depends(get_current_active_user),
service: AgentChatService = Depends(get_agent_chat_service),
) -> _SchemaResponse:
"""
获取一条 Agent 历史会话详情。
:param session_id: Agent 会话 ID
:param current_user: 当前登录用户
:param db: 异步数据库会话
:param service: Agent 会话应用服务
:return: 会话详情
"""
oper = AgentChatOper(db)
chat = await _get_accessible_agent_chat(oper, session_id, current_user)
chat = await _get_accessible_agent_chat(service, session_id, current_user)
server_session_id = session_id
if not chat:
server_session_id = _build_web_agent_session_id(current_user, session_id)
if server_session_id != session_id:
chat = await _get_accessible_agent_chat(oper, server_session_id, current_user)
chat = await _get_accessible_agent_chat(
service,
server_session_id,
current_user,
)
if not chat:
manager = get_running_agent_manager()
if manager and manager.is_session_busy(server_session_id):
@@ -1803,7 +1800,7 @@ async def get_agent_chat_session(
},
)
return _SchemaResponse(success=False, message="会话不存在或无权访问")
data = AgentChatOper.to_detail(chat)
data = service.to_detail(chat).model_dump()
manager = get_running_agent_manager()
data["is_processing"] = bool(
manager and manager.is_session_busy(chat.session_id)
@@ -1819,8 +1816,8 @@ async def get_agent_chat_session(
async def save_agent_chat_display(
session_id: str,
payload: _SchemaAgentChatDisplaySaveRequest,
current_user: User = Depends(get_current_active_user),
db: AsyncSession = Depends(get_async_db),
current_user: ApiPrincipal = Depends(get_current_active_user),
service: AgentChatService = Depends(get_agent_chat_service),
) -> _SchemaResponse:
"""
保存前端聚合后的 Agent 展示消息。
@@ -1828,12 +1825,15 @@ async def save_agent_chat_display(
:param session_id: Agent 会话 ID
:param payload: 展示消息保存请求
:param current_user: 当前登录用户
:param db: 异步数据库会话
:param service: Agent 会话应用服务
:return: 保存后的会话摘要
"""
oper = AgentChatOper(db)
existing_chat = await oper.async_get(session_id=session_id)
if existing_chat and not _can_access_agent_chat(existing_chat, current_user):
existing_chat = await service.get_accessible(session_id, current_user)
if existing_chat is None:
unrestricted_chat = await service.get(session_id)
else:
unrestricted_chat = existing_chat
if unrestricted_chat and existing_chat is None:
return _SchemaResponse(success=False, message="会话不存在或无权访问")
messages = [
@@ -1847,10 +1847,10 @@ async def save_agent_chat_display(
messages=messages,
client_session_id=existing_chat.client_session_id if existing_chat else session_id,
)
chat = await oper.async_get(session_id=session_id)
chat = await service.get_accessible(session_id, current_user)
if not chat:
return _SchemaResponse(success=False, message="会话保存失败")
return _SchemaResponse(success=True, data=AgentChatOper.to_summary(chat))
return _SchemaResponse(success=True, data=service.to_summary(chat))
@router.delete(
@@ -1860,22 +1860,21 @@ async def save_agent_chat_display(
)
async def delete_agent_chat_session(
session_id: str,
current_user: User = Depends(get_current_active_user),
db: AsyncSession = Depends(get_async_db),
current_user: ApiPrincipal = Depends(get_current_active_user),
service: AgentChatService = Depends(get_agent_chat_service),
) -> _SchemaResponse:
"""
删除一条 Agent 历史会话。
:param session_id: Agent 会话 ID
:param current_user: 当前登录用户
:param db: 异步数据库会话
:param service: Agent 会话应用服务
:return: 删除结果
"""
oper = AgentChatOper(db)
chat = await _get_accessible_agent_chat(oper, session_id, current_user)
chat = await _get_accessible_agent_chat(service, session_id, current_user)
if not chat:
return _SchemaResponse(success=False, message="会话不存在或无权访问")
deleted = await oper.async_delete(session_id=session_id)
deleted = await service.delete(session_id, current_user)
return _SchemaResponse(success=deleted, message="删除成功" if deleted else "删除失败")
@@ -1886,23 +1885,25 @@ async def delete_agent_chat_session(
)
async def stop_web_agent_session_task(
session_id: str,
current_user: User = Depends(get_current_active_user),
db: AsyncSession = Depends(get_async_db),
current_user: ApiPrincipal = Depends(get_current_active_user),
service: AgentChatService = Depends(get_agent_chat_service),
) -> _SchemaResponse:
"""
停止当前 Web 智能助手会话正在执行的任务。
:param session_id: Agent 会话 ID
:param current_user: 当前登录用户
:param db: 异步数据库会话
:param service: Agent 会话应用服务
:return: 停止结果
"""
server_session_id = _build_web_agent_session_id(current_user, session_id)
chat = await _get_accessible_agent_chat(
AgentChatOper(db), server_session_id, current_user
service,
server_session_id,
current_user,
)
if not chat and server_session_id != session_id:
chat = await _get_accessible_agent_chat(AgentChatOper(db), session_id, current_user)
chat = await _get_accessible_agent_chat(service, session_id, current_user)
if chat and not _can_access_agent_chat(chat, current_user):
return _SchemaResponse(success=False, message="会话不存在或无权访问")
@@ -1930,7 +1931,7 @@ async def stop_web_agent_session_task(
async def web_agent_stream(
payload: _SchemaAgentWebChatRequest,
request: Request,
current_user: User = Depends(get_current_active_user),
current_user: ApiPrincipal = Depends(get_current_active_user),
) -> StreamingResponse:
"""
Web 智能助手流式对话。