feat: 新增 agent/app/core 目录

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 14:26:53 +08:00
parent 8249f67351
commit 765a968e63
13 changed files with 2706 additions and 0 deletions

View File

@@ -0,0 +1,379 @@
"""
通知工具
提供发送通知的功能邮件、Webhook等
"""
import httpx
from typing import Dict, Any, Optional, List
from dataclasses import dataclass
from enum import Enum
class NotificationType(Enum):
"""通知类型"""
EMAIL = "email"
WEBHOOK = "webhook"
SMS = "sms"
DINGTALK = "dingtalk"
WECHAT = "wechat"
SLACK = "slack"
@dataclass
class NotificationConfig:
"""通知配置"""
# Email配置
smtp_host: str = ""
smtp_port: int = 587
smtp_user: str = ""
smtp_password: str = ""
from_email: str = ""
# Webhook配置
webhook_url: str = ""
webhook_secret: str = ""
# 钉钉配置
dingtalk_webhook: str = ""
# Slack配置
slack_webhook: str = ""
class NotificationTool:
"""
通知工具
支持多种通知渠道:
- Email (SMTP)
- Webhook
- 钉钉
- Slack
"""
def __init__(self, config: Optional[NotificationConfig] = None):
self.config = config or NotificationConfig()
async def send_email(
self,
to: str,
subject: str,
body: str,
cc: Optional[List[str]] = None,
is_html: bool = False
) -> Dict[str, Any]:
"""
发送邮件
Args:
to: 收件人
subject: 主题
body: 内容
cc: 抄送列表
is_html: 是否HTML格式
Returns:
发送结果
"""
if not self.config.smtp_host:
return {
"success": False,
"error": "Email not configured"
}
try:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# 构建邮件
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = self.config.from_email or self.config.smtp_user
msg['To'] = to
if cc:
msg['Cc'] = ",".join(cc)
# 添加内容
content_type = "html" if is_html else "plain"
msg.attach(MIMEText(body, content_type))
# 发送
with smtplib.SMTP(self.config.smtp_host, self.config.smtp_port) as server:
server.starttls()
server.login(self.config.smtp_user, self.config.smtp_password)
server.send_message(msg)
return {
"success": True,
"type": "email",
"to": to,
"subject": subject
}
except Exception as e:
return {
"success": False,
"error": str(e),
"type": "email"
}
async def send_webhook(
self,
url: str,
data: Dict[str, Any],
method: str = "POST",
headers: Optional[Dict[str, str]] = None
) -> Dict[str, Any]:
"""
发送Webhook
Args:
url: Webhook URL
data: 请求数据
method: HTTP方法
headers: 请求头
Returns:
发送结果
"""
try:
async with httpx.AsyncClient(timeout=10) as client:
response = await client.request(
method=method,
url=url,
json=data,
headers=headers
)
return {
"success": response.status_code < 400,
"status_code": response.status_code,
"type": "webhook",
"url": url
}
except Exception as e:
return {
"success": False,
"error": str(e),
"type": "webhook"
}
async def send_dingtalk(
self,
message: str,
webhook: Optional[str] = None
) -> Dict[str, Any]:
"""
发送钉钉消息
Args:
message: 消息内容
webhook: 自定义webhook URL
Returns:
发送结果
"""
url = webhook or self.config.dingtalk_webhook
if not url:
return {
"success": False,
"error": "Dingtalk webhook not configured"
}
try:
async with httpx.AsyncClient(timeout=10) as client:
response = await client.post(
url,
json={
"msgtype": "text",
"text": {
"content": message
}
}
)
result = response.json()
return {
"success": result.get("errcode") == 0,
"type": "dingtalk",
"response": result
}
except Exception as e:
return {
"success": False,
"error": str(e),
"type": "dingtalk"
}
async def send_slack(
self,
message: str,
channel: Optional[str] = None,
webhook: Optional[str] = None
) -> Dict[str, Any]:
"""
发送Slack消息
Args:
message: 消息内容
channel: 频道
webhook: 自定义webhook URL
Returns:
发送结果
"""
url = webhook or self.config.slack_webhook
if not url:
return {
"success": False,
"error": "Slack webhook not configured"
}
try:
payload = {"text": message}
if channel:
payload["channel"] = channel
async with httpx.AsyncClient(timeout=10) as client:
response = await client.post(url, json=payload)
return {
"success": response.status_code == 200,
"type": "slack",
"status_code": response.status_code
}
except Exception as e:
return {
"success": False,
"error": str(e),
"type": "slack"
}
async def send(
self,
type: str,
message: str,
**kwargs
) -> Dict[str, Any]:
"""
统一发送接口
Args:
type: 通知类型 (email, webhook, dingtalk, slack)
message: 消息内容
**kwargs: 其他参数
Returns:
发送结果
"""
type = type.lower()
if type == "email":
return await self.send_email(
to=kwargs.get("to", ""),
subject=kwargs.get("subject", "Notification"),
body=message,
cc=kwargs.get("cc")
)
elif type == "webhook":
return await self.send_webhook(
url=kwargs.get("url", ""),
data=kwargs.get("data", {"message": message})
)
elif type == "dingtalk":
return await self.send_dingtalk(
message=message,
webhook=kwargs.get("webhook")
)
elif type == "slack":
return await self.send_slack(
message=message,
channel=kwargs.get("channel"),
webhook=kwargs.get("webhook")
)
else:
return {
"success": False,
"error": f"Unknown notification type: {type}"
}
# 全局通知工具
notification_tool = NotificationTool()
# 便捷函数
async def send_notification(
type: str,
message: str,
**kwargs
) -> Dict[str, Any]:
"""发送通知"""
return await notification_tool.send(type, message, **kwargs)
async def send_email(
to: str,
subject: str,
body: str
) -> Dict[str, Any]:
"""发送邮件"""
return await notification_tool.send_email(to, subject, body)
async def send_webhook(
url: str,
data: Dict[str, Any]
) -> Dict[str, Any]:
"""发送Webhook"""
return await notification_tool.send_webhook(url, data)
# 工具定义
SEND_NOTIFICATION_TOOL = {
"name": "send_notification",
"description": "Send notifications via email, webhook, dingtalk, or slack.",
"parameters": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Notification type: email, webhook, dingtalk, slack",
"enum": ["email", "webhook", "dingtalk", "slack"]
},
"message": {
"type": "string",
"description": "The notification message"
},
"to": {
"type": "string",
"description": "For email: recipient email address"
},
"subject": {
"type": "string",
"description": "For email: email subject"
},
"url": {
"type": "string",
"description": "For webhook: webhook URL"
},
"data": {
"type": "object",
"description": "For webhook: JSON data to send"
},
"webhook": {
"type": "string",
"description": "Custom webhook URL for dingtalk/slack"
},
"channel": {
"type": "string",
"description": "For slack: channel name"
}
},
"required": ["type", "message"]
}
}