新增渠道扩展框架的核心基础层,为所有渠道扩展提供统一的基础设施。 主要变更: - channel/extensions/__init__.py: 渠道扩展包初始化与注册 - channel/extensions/base.py: 渠道插件基类,定义统一接口规范 - channel_service.py: 渠道服务核心业务逻辑 - models_channels.py: 渠道相关数据库模型 - 8 个 channel_*_repo.py: 渠道数据仓库层 - storage/postgres/manager.py: 数据库管理器,新增渠道表 - models_business.py: 业务模型更新 - config/app.py: 应用配置更新 - conversation_repository.py: 会话仓库更新 - oidc_service.py: OIDC 服务更新 - logging_config.py: 日志配置更新
245 lines
8.1 KiB
Python
245 lines
8.1 KiB
Python
from typing import Any
|
|
|
|
from yuxi.channel.runtime.manager import gateway
|
|
from yuxi.channel.security.pairing import PairingManager
|
|
from yuxi.repositories.channel_binding_repo import ChannelBindingRepository
|
|
from yuxi.repositories.channel_config_repo import ChannelConfigRepository
|
|
from yuxi.repositories.channel_pairing_repo import ChannelPairingRecordRepository
|
|
from yuxi.repositories.channel_thread_mapping_repo import ChannelThreadMappingRepository
|
|
from yuxi.repositories.channel_user_mapping_repo import ChannelUserMappingRepository
|
|
|
|
_PAIRING_PEER_ID_PREFIX = "admin:pairing"
|
|
|
|
_config_repo = ChannelConfigRepository()
|
|
_binding_repo = ChannelBindingRepository()
|
|
_pairing_repo = ChannelPairingRecordRepository()
|
|
_user_mapping_repo = ChannelUserMappingRepository()
|
|
_thread_mapping_repo = ChannelThreadMappingRepository()
|
|
|
|
|
|
async def list_channel_configs(
|
|
channel_type: str | None = None,
|
|
enabled: bool | None = None,
|
|
) -> list[dict[str, Any]]:
|
|
if channel_type and enabled is not None:
|
|
configs = await _config_repo.list_enabled(channel_type)
|
|
elif enabled is not None:
|
|
configs = await _config_repo.list_enabled()
|
|
elif channel_type:
|
|
configs = await _config_repo.list_by_type(channel_type)
|
|
else:
|
|
configs = await _config_repo.list_all()
|
|
return [c.to_dict(mask_secrets=True) for c in configs]
|
|
|
|
|
|
async def get_channel_config(config_id: str) -> dict[str, Any] | None:
|
|
config = await _config_repo.get_by_id(config_id)
|
|
return config.to_dict(mask_secrets=True) if config else None
|
|
|
|
|
|
async def create_channel_config(data: dict[str, Any]) -> dict[str, Any]:
|
|
config = await _config_repo.create(data)
|
|
return config.to_dict(mask_secrets=True)
|
|
|
|
|
|
async def update_channel_config(config_id: str, data: dict[str, Any]) -> dict[str, Any] | None:
|
|
config = await _config_repo.update(config_id, data)
|
|
return config.to_dict(mask_secrets=True) if config else None
|
|
|
|
|
|
async def delete_channel_config(config_id: str) -> bool:
|
|
return await _config_repo.delete(config_id)
|
|
|
|
|
|
async def list_channel_bindings(agent_config_id: int | None = None) -> list[dict[str, Any]]:
|
|
if agent_config_id is not None:
|
|
bindings = await _binding_repo.list_by_agent(agent_config_id)
|
|
else:
|
|
bindings = await _binding_repo.list_all()
|
|
return [b.to_dict() for b in bindings]
|
|
|
|
|
|
async def get_channel_binding(binding_id: str) -> dict[str, Any] | None:
|
|
binding = await _binding_repo.get_by_id(binding_id)
|
|
return binding.to_dict() if binding else None
|
|
|
|
|
|
async def create_channel_binding(data: dict[str, Any]) -> dict[str, Any]:
|
|
binding = await _binding_repo.create(data)
|
|
return binding.to_dict()
|
|
|
|
|
|
async def update_channel_binding(binding_id: str, data: dict[str, Any]) -> dict[str, Any] | None:
|
|
binding = await _binding_repo.update(binding_id, data)
|
|
return binding.to_dict() if binding else None
|
|
|
|
|
|
async def delete_channel_binding(binding_id: str) -> bool:
|
|
return await _binding_repo.delete(binding_id)
|
|
|
|
|
|
async def list_channel_bindings_by_type(channel_type: str) -> list[dict[str, Any]]:
|
|
bindings = await _binding_repo.list_by_channel_type(channel_type)
|
|
return [b.to_dict() for b in bindings]
|
|
|
|
|
|
async def list_channel_pairing_records(status: str | None = None) -> list[dict[str, Any]]:
|
|
records = await _pairing_repo.list_all(status=status)
|
|
return [r.to_dict() for r in records]
|
|
|
|
|
|
async def approve_channel_pairing(record_id: str) -> dict[str, Any] | None:
|
|
record = await _pairing_repo.approve(record_id)
|
|
return record.to_dict() if record else None
|
|
|
|
|
|
async def reject_channel_pairing(record_id: str) -> dict[str, Any] | None:
|
|
record = await _pairing_repo.reject(record_id)
|
|
return record.to_dict() if record else None
|
|
|
|
|
|
async def get_channel_pairing_record(record_id: str) -> dict[str, Any] | None:
|
|
record = await _pairing_repo.get_by_id(record_id)
|
|
return record.to_dict() if record else None
|
|
|
|
|
|
async def delete_channel_pairing_record(record_id: str) -> bool:
|
|
return await _pairing_repo.delete(record_id)
|
|
|
|
|
|
async def generate_agent_pairing_code(agent_config_id: int) -> dict[str, Any]:
|
|
bindings = await _binding_repo.list_by_agent(agent_config_id)
|
|
if not bindings:
|
|
raise ValueError(f"Agent config {agent_config_id} has no channel bindings")
|
|
|
|
binding = bindings[0]
|
|
mgr = PairingManager()
|
|
peer_id = f"{_PAIRING_PEER_ID_PREFIX}:{agent_config_id}"
|
|
|
|
req = await mgr.upsert_code(
|
|
channel_type=binding.channel_type,
|
|
peer_id=peer_id,
|
|
account_id=binding.account_id or "default",
|
|
)
|
|
|
|
return {
|
|
"code": req.code,
|
|
"channel_type": binding.channel_type,
|
|
"account_id": binding.account_id or "default",
|
|
"agent_config_id": agent_config_id,
|
|
"record_id": req._record_id,
|
|
}
|
|
|
|
|
|
async def list_user_mappings() -> list[dict[str, Any]]:
|
|
mappings = await _user_mapping_repo.list_all()
|
|
return [m.to_dict() for m in mappings]
|
|
|
|
|
|
async def get_user_mapping(mapping_id: int) -> dict[str, Any] | None:
|
|
mapping = await _user_mapping_repo.get_by_id(mapping_id)
|
|
return mapping.to_dict() if mapping else None
|
|
|
|
|
|
async def find_user_mapping_by_channel(channel_id: str, channel_user_id: str) -> dict[str, Any] | None:
|
|
mapping = await _user_mapping_repo.find_by_channel(channel_id, channel_user_id)
|
|
return mapping.to_dict() if mapping else None
|
|
|
|
|
|
async def resolve_channel_user(channel_id: str, channel_user_id: str) -> dict[str, Any]:
|
|
mapping = await _user_mapping_repo.resolve_user(channel_id, channel_user_id)
|
|
return mapping.to_dict()
|
|
|
|
|
|
async def find_user_mappings_by_internal_user(internal_user_id: str) -> list[dict[str, Any]]:
|
|
mappings = await _user_mapping_repo.find_by_internal_user(internal_user_id)
|
|
return [m.to_dict() for m in mappings]
|
|
|
|
|
|
async def create_user_mapping(data: dict[str, Any]) -> dict[str, Any]:
|
|
mapping = await _user_mapping_repo.create(data)
|
|
return mapping.to_dict()
|
|
|
|
|
|
async def delete_user_mapping(mapping_id: int) -> bool:
|
|
return await _user_mapping_repo.delete(mapping_id)
|
|
|
|
|
|
async def resolve_channel_thread(
|
|
channel_id: str,
|
|
channel_chat_id: str,
|
|
internal_user_id: str,
|
|
agent_id: str | None = None,
|
|
) -> dict[str, Any]:
|
|
mapping = await _thread_mapping_repo.resolve_thread(
|
|
channel_id,
|
|
channel_chat_id,
|
|
internal_user_id,
|
|
agent_id,
|
|
)
|
|
return mapping.to_dict()
|
|
|
|
|
|
async def get_thread_mapping(mapping_id: int) -> dict[str, Any] | None:
|
|
mapping = await _thread_mapping_repo.get_by_id(mapping_id)
|
|
return mapping.to_dict() if mapping else None
|
|
|
|
|
|
async def find_thread_mapping_by_channel_chat(
|
|
channel_id: str,
|
|
channel_chat_id: str,
|
|
internal_user_id: str,
|
|
) -> dict[str, Any] | None:
|
|
mapping = await _thread_mapping_repo.find_by_channel_chat(
|
|
channel_id,
|
|
channel_chat_id,
|
|
internal_user_id,
|
|
)
|
|
return mapping.to_dict() if mapping else None
|
|
|
|
|
|
async def find_thread_mapping_by_thread_id(thread_id: str) -> dict[str, Any] | None:
|
|
mapping = await _thread_mapping_repo.find_by_thread_id(thread_id)
|
|
return mapping.to_dict() if mapping else None
|
|
|
|
|
|
async def create_thread_mapping(data: dict[str, Any]) -> dict[str, Any]:
|
|
mapping = await _thread_mapping_repo.create(data)
|
|
return mapping.to_dict()
|
|
|
|
|
|
async def delete_thread_mapping(mapping_id: int) -> bool:
|
|
return await _thread_mapping_repo.delete(mapping_id)
|
|
|
|
|
|
async def get_gateway_health() -> dict[str, Any]:
|
|
report = gateway.get_health()
|
|
return {
|
|
"status": report.status,
|
|
"channels": report.channels,
|
|
"summary": report.summary,
|
|
"boot": gateway.get_boot_status().to_dict(),
|
|
}
|
|
|
|
|
|
async def get_gateway_healthz() -> dict[str, Any]:
|
|
return {"status": "ok"}
|
|
|
|
|
|
async def get_hook_status() -> dict[str, Any]:
|
|
result = gateway.get_hook_status()
|
|
return result.to_dict()
|
|
|
|
|
|
async def reload_hooks() -> dict[str, Any]:
|
|
result = gateway.reload_hooks()
|
|
return result.to_dict()
|
|
|
|
|
|
async def install_hook(hook_id: str, hook_md_content: str, target: str = "workspace") -> dict[str, Any]:
|
|
return gateway.install_hook(hook_id, hook_md_content, target)
|
|
|
|
|
|
async def uninstall_hook(source: str, hook_id: str) -> dict[str, Any]:
|
|
return gateway.uninstall_hook(source, hook_id)
|