ForcePilot/backend/package/yuxi/channel/extensions/jira/config.py
Kris c5d8e3550e feat(channel): 添加 Jira 渠道扩展
新增 Jira 渠道扩展,支持在 Yuxi 平台中集成 Jira 项目管理渠道。

包含以下功能模块:
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- outbound: 外发消息与工单管理
- streaming: 流式消息处理
- parse: Jira 内容解析
- format: 消息格式转换
- agent_tools: Agent 工具集成
- security: 安全校验
- dedupe: 消息去重
- loopbreaker: 循环响应防护
- monitor: 渠道状态监控
- status: 会话与工单状态管理
- types: 类型定义
2026-05-21 11:10:54 +08:00

214 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import hashlib
import logging
import os
from yuxi.channel.protocols import ConfigProtocol
logger = logging.getLogger(__name__)
class JiraConfigAdapter(ConfigProtocol):
def __init__(self):
self._config: dict = {}
@property
def _jira_cfg(self) -> dict:
return self._config.get("channels", {}).get("jira", {})
def list_account_ids(self, config: dict) -> list[str]:
self._config = config
accounts = self._jira_cfg.get("accounts", {})
if accounts:
return list(accounts.keys())
if self._env_credentials_exist():
return ["default"]
return []
async def resolve_account(self, account_id: str) -> dict:
return self._build_account(account_id)
def is_configured(self, account: dict) -> bool:
return bool(account.get("site_url") and account.get("email") and account.get("api_token"))
def is_enabled(self, account: dict, config: dict | None = None) -> bool:
return account.get("enabled", True)
def disabled_reason(self, account: dict, config: dict | None = None) -> str:
return "" if self.is_enabled(account, config) else "账户已禁用"
def unconfigured_reason(self, account: dict, config: dict | None = None) -> str:
return "" if self.is_configured(account) else "未配置 site_url/email/api_token"
def describe_account(self, account: dict, config: dict | None = None) -> dict:
return {
"account_id": account.get("account_id", ""),
"name": account.get("name", ""),
"site_url": account.get("site_url", ""),
"project_key": account.get("project_key", ""),
"auth_method": account.get("auth_method", "api_token"),
"bot_display_name": account.get("bot_display_name", ""),
"comment_visibility": account.get("comment_visibility", "internal"),
"configured": self.is_configured(account),
}
def inspect_account(self, config: dict, account_id: str | None = None) -> dict:
return {}
def default_account_id(self, config: dict | None = None) -> str:
return self._jira_cfg.get("defaultAccount", "default")
def set_account_enabled(self, config: dict, account_id: str, enabled: bool) -> dict:
return config
def delete_account(self, config: dict, account_id: str) -> dict:
return config
def resolve_allow_from(self, config: dict, account_id: str | None = None) -> list[str | int] | None:
return None
def format_allow_from(self, config: dict, account_id: str | None, allow_from: list[str | int]) -> list[str]:
return [str(e) for e in allow_from]
def has_configured_state(self, config: dict) -> bool:
return bool(self._jira_cfg)
def has_persisted_auth_state(self, config: dict) -> bool:
return True
def resolve_default_to(self, config: dict, account_id: str | None = None) -> str | None:
return None
@staticmethod
def credential_fingerprint(site_url: str, email: str, api_token: str) -> str:
return hashlib.sha256(f"{site_url}:{email}:{api_token}".encode()).hexdigest()[:8]
@staticmethod
def _env_credentials_exist() -> bool:
return all(
[
os.environ.get("JIRA_SITE_URL", ""),
os.environ.get("JIRA_EMAIL", ""),
os.environ.get("JIRA_API_TOKEN", ""),
]
)
def _build_account(self, account_id: str) -> dict:
jira_cfg = self._jira_cfg
accounts = jira_cfg.get("accounts", {})
account_raw = accounts.get(account_id, {}) if account_id != "default" else jira_cfg
def _get(key: str, default=None):
return account_raw.get(key, jira_cfg.get(key, default))
site_url = os.environ.get("JIRA_SITE_URL", "")
email = os.environ.get("JIRA_EMAIL", "")
api_token = os.environ.get("JIRA_API_TOKEN", "")
if not site_url:
site_url = _get("siteUrl", "")
if not email:
email = _get("email", "")
if not api_token:
api_token = _get("apiToken", "")
token_source = (
"env"
if (site_url and email and api_token and os.environ.get("JIRA_SITE_URL"))
else "config"
if _get("apiToken")
else "none"
)
return {
"account_id": account_id,
"site_url": site_url.rstrip("/") if site_url else "",
"email": email,
"api_token": api_token,
"auth_method": _get("authMethod", "api_token"),
"project_key": _get("projectKey", ""),
"jql_filter": _get("jqlFilter", ""),
"callback_base_url": _get("callbackBaseUrl", ""),
"webhook_secret": _get("webhookSecret", ""),
"webhook_id": _get("webhookId", ""),
"bot_account_id": _get("botAccountId", ""),
"bot_display_name": _get("botDisplayName", ""),
"comment_visibility": _get("commentVisibility", "internal"),
"streaming_mode": _get("streamingMode", "block"),
"allowed_projects": _get("allowedProjects", []),
"enabled": account_raw.get("enabled", True),
"name": account_raw.get("name", account_id),
"token_source": token_source,
}
def config_schema(self) -> dict:
return {
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Jira 渠道配置",
"properties": {
"siteUrl": {
"type": "string",
"title": "Jira Site URL",
"description": "Jira Cloud URL如 https://your-domain.atlassian.net",
"format": "uri",
},
"email": {
"type": "string",
"title": "Email",
"description": "Jira 账号邮箱API Token 方式必填)",
"format": "email",
},
"apiToken": {
"type": "string",
"title": "API Token",
"description": "在 https://id.atlassian.com/manage/api-tokens 生成",
"x-ui-password": True,
},
"projectKey": {
"type": "string",
"title": "Project Key",
"description": "Bot 作用于哪个 Jira Project如 SUPPORT",
},
"allowedProjects": {
"type": "array",
"title": "允许响应的项目",
"description": "Bot 可以响应的 Project Key 列表,留空则响应所有",
"items": {"type": "string"},
},
"jqlFilter": {
"type": "string",
"title": "JQL 过滤",
"description": "Webhook 事件的 JQL 过滤条件,默认 project={projectKey}",
},
"callbackBaseUrl": {
"type": "string",
"title": "Webhook 回调地址",
"description": "ForcePilot 的公网可达 URL如 https://forcepilot.example.com",
"format": "uri",
},
"commentVisibility": {
"type": "string",
"title": "默认评论可见性",
"enum": ["internal", "public"],
"default": "internal",
"description": "internal=仅内部可见public=客户可见JSM",
},
"streamingMode": {
"type": "string",
"title": "流式输出模式",
"enum": ["off", "block"],
"default": "block",
"description": "block=占位评论+定时全量更新",
},
"webhookSecret": {
"type": "string",
"title": "Webhook Secret",
"description": "Jira Webhook Secret Token用于 HMAC-SHA256 签名验证",
"x-ui-password": True,
},
},
"required": ["siteUrl", "email", "apiToken", "callbackBaseUrl"],
}