feat(agents): Phase 7-10 API endpoints for hooks, plugins, skills, sessions
This commit is contained in:
241
backend/app/routers/hooks.py
Normal file
241
backend/app/routers/hooks.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""Hook API 路由 - Phase 7.5"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.agents.tools.hooks import HookType
|
||||
from app.agents.tools.hooks.builtins import (
|
||||
AuditLogHook,
|
||||
DangerousConfirmationHook,
|
||||
SecurityScanHook,
|
||||
)
|
||||
from app.agents.tools.hooks.config import (
|
||||
HookConfigEntry,
|
||||
get_hook_config_persistence,
|
||||
)
|
||||
from app.agents.tools.hooks.manager import get_hook_manager
|
||||
|
||||
router = APIRouter(prefix="/api/hooks", tags=["Hooks"])
|
||||
|
||||
|
||||
class HookInfo(BaseModel):
|
||||
"""Hook 信息"""
|
||||
|
||||
name: str
|
||||
hook_type: str
|
||||
description: str
|
||||
builtin: bool
|
||||
|
||||
|
||||
class HookConfigUpdate(BaseModel):
|
||||
"""更新 Hook 配置"""
|
||||
|
||||
entries: list[HookConfigEntry]
|
||||
|
||||
|
||||
class HookConfigResponse(BaseModel):
|
||||
"""Hook 配置响应"""
|
||||
|
||||
entries: list[dict[str, Any]]
|
||||
count: int
|
||||
|
||||
|
||||
class HookStatusResponse(BaseModel):
|
||||
"""Hook 状态响应"""
|
||||
|
||||
name: str
|
||||
enabled: bool
|
||||
hook_type: str
|
||||
registered: bool
|
||||
|
||||
|
||||
# 内置 Hook 注册表
|
||||
BUILTIN_HOOKS: dict[str, dict[str, str]] = {
|
||||
"audit_log": {
|
||||
"name": "audit_log",
|
||||
"hook_type": "pre_tool_use,post_tool_use,tool_error",
|
||||
"description": "审计日志 Hook - 记录所有工具调用",
|
||||
"class": "AuditLogHook",
|
||||
},
|
||||
"dangerous_confirmation": {
|
||||
"name": "dangerous_confirmation",
|
||||
"hook_type": "pre_tool_use",
|
||||
"description": "危险操作确认 Hook - 拦截危险工具调用",
|
||||
"class": "DangerousConfirmationHook",
|
||||
},
|
||||
"security_scan": {
|
||||
"name": "security_scan",
|
||||
"hook_type": "post_tool_use",
|
||||
"description": "安全扫描 Hook - 检测敏感信息泄露",
|
||||
"class": "SecurityScanHook",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/available", response_model=list[HookInfo])
|
||||
async def list_available_hooks() -> list[HookInfo]:
|
||||
"""列出所有可用的内置 Hook"""
|
||||
return [
|
||||
HookInfo(
|
||||
name=info["name"],
|
||||
hook_type=info["hook_type"],
|
||||
description=info["description"],
|
||||
builtin=True,
|
||||
)
|
||||
for info in BUILTIN_HOOKS.values()
|
||||
]
|
||||
|
||||
|
||||
@router.get("/config", response_model=HookConfigResponse)
|
||||
async def get_hook_config() -> HookConfigResponse:
|
||||
"""获取当前 Hook 配置"""
|
||||
persistence = get_hook_config_persistence()
|
||||
entries = persistence.load_config()
|
||||
return HookConfigResponse(
|
||||
entries=[vars(e) if isinstance(e, HookConfigEntry) else e for e in entries],
|
||||
count=len(entries),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/config", response_model=HookConfigResponse)
|
||||
async def update_hook_config(
|
||||
entries: list[HookConfigEntry],
|
||||
) -> HookConfigResponse:
|
||||
"""更新 Hook 配置"""
|
||||
persistence = get_hook_config_persistence()
|
||||
success = persistence.save_config(entries)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to save hook config")
|
||||
|
||||
# 应用配置到 HookManager
|
||||
manager = get_hook_manager()
|
||||
manager.clear() # 清除旧配置
|
||||
persistence.apply_config() # 应用新配置
|
||||
|
||||
return HookConfigResponse(
|
||||
entries=[vars(e) if isinstance(e, HookConfigEntry) else e for e in entries],
|
||||
count=len(entries),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/apply-config", response_model=dict[str, Any])
|
||||
async def apply_hook_config() -> dict[str, Any]:
|
||||
"""应用配置文件到 HookManager"""
|
||||
persistence = get_hook_config_persistence()
|
||||
manager = get_hook_manager()
|
||||
manager.clear()
|
||||
count = persistence.apply_config()
|
||||
return {"applied": count, "message": f"Applied {count} hook configurations"}
|
||||
|
||||
|
||||
@router.get("/status", response_model=list[HookStatusResponse])
|
||||
async def get_hook_status() -> list[HookStatusResponse]:
|
||||
"""获取所有已注册 Hook 的状态"""
|
||||
manager = get_hook_manager()
|
||||
all_hooks = manager.list_all()
|
||||
|
||||
# 按名称索引已注册的 hooks
|
||||
registered: dict[str, dict[str, Any]] = {}
|
||||
for hook in all_hooks:
|
||||
registered[hook.name] = {
|
||||
"name": hook.name,
|
||||
"enabled": hook.enabled,
|
||||
"hook_type": hook.hook_type.value,
|
||||
"registered": True,
|
||||
}
|
||||
|
||||
# 合并内置 Hook 信息
|
||||
result: list[HookStatusResponse] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
# 先添加已注册的
|
||||
for hook in all_hooks:
|
||||
result.append(
|
||||
HookStatusResponse(
|
||||
name=hook.name,
|
||||
enabled=hook.enabled,
|
||||
hook_type=hook.hook_type.value,
|
||||
registered=True,
|
||||
)
|
||||
)
|
||||
seen.add(hook.name)
|
||||
|
||||
# 再添加内置但未注册的
|
||||
for name, info in BUILTIN_HOOKS.items():
|
||||
if name not in seen:
|
||||
result.append(
|
||||
HookStatusResponse(
|
||||
name=name,
|
||||
enabled=False,
|
||||
hook_type=info["hook_type"],
|
||||
registered=False,
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/{name}/enable", response_model=dict[str, str])
|
||||
async def enable_hook(name: str) -> dict[str, str]:
|
||||
"""启用指定 Hook"""
|
||||
manager = get_hook_manager()
|
||||
if manager.enable(name):
|
||||
return {"status": "enabled", "name": name}
|
||||
raise HTTPException(status_code=404, detail=f"Hook '{name}' not found")
|
||||
|
||||
|
||||
@router.post("/{name}/disable", response_model=dict[str, str])
|
||||
async def disable_hook(name: str) -> dict[str, str]:
|
||||
"""禁用指定 Hook"""
|
||||
manager = get_hook_manager()
|
||||
if manager.disable(name):
|
||||
return {"status": "disabled", "name": name}
|
||||
raise HTTPException(status_code=404, detail=f"Hook '{name}' not found")
|
||||
|
||||
|
||||
@router.post("/register-builtin", response_model=dict[str, str])
|
||||
async def register_builtin_hook(
|
||||
name: str,
|
||||
hook_type: str = "pre_tool_use",
|
||||
) -> dict[str, str]:
|
||||
"""注册内置 Hook 到 HookManager"""
|
||||
from app.agents.tools.hooks.types import HookDefinition, HookTrigger
|
||||
|
||||
manager = get_hook_manager()
|
||||
|
||||
if name == "audit_log":
|
||||
hook_instance = AuditLogHook()
|
||||
handler = hook_instance.pre_tool_use
|
||||
hook_types = [HookType.PRE_TOOL_USE, HookType.POST_TOOL_USE, HookType.TOOL_ERROR]
|
||||
elif name == "dangerous_confirmation":
|
||||
hook_instance = DangerousConfirmationHook()
|
||||
handler = hook_instance.pre_tool_use
|
||||
hook_types = [HookType.PRE_TOOL_USE]
|
||||
elif name == "security_scan":
|
||||
hook_instance = SecurityScanHook()
|
||||
handler = hook_instance.post_tool_use
|
||||
hook_types = [HookType.POST_TOOL_USE]
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"Unknown builtin hook: {name}")
|
||||
|
||||
registered = []
|
||||
for ht in hook_types:
|
||||
hook_def = HookDefinition(
|
||||
name=f"{name}_{ht.value}",
|
||||
hook_type=ht,
|
||||
trigger=HookTrigger(),
|
||||
handler=handler,
|
||||
priority=0,
|
||||
enabled=True,
|
||||
description=f"Built-in {name} hook",
|
||||
)
|
||||
manager.register(hook_def)
|
||||
registered.append(ht.value)
|
||||
|
||||
return {
|
||||
"status": "registered",
|
||||
"name": name,
|
||||
"hook_types": ", ".join(registered),
|
||||
}
|
||||
Reference in New Issue
Block a user