新增 Microsoft Teams 渠道扩展,支持在 Yuxi 平台中集成 Microsoft Teams 协作平台。 包含以下功能模块: - sdk: Bot Framework SDK 封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - auth: JWT 认证 - jwks: JWKS 密钥管理 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session: 会话管理 - state: 状态管理 - runtime: 运行时管理 - actions: 动作处理 - adaptive_card: 自适应卡片 - task_modules: 任务模块 - message_extension: 消息扩展 - proactive: Proactive Messaging - graph: Microsoft Graph API 集成 - graph_teams: Teams 操作 - graph_members: 成员管理 - graph_messages: 消息获取 - graph_thread: 线程管理 - graph_users: 用户管理 - graph_upload: 文件上传 - files: 文件处理 - file_consent: 文件授权 - conversations: 会话存储 - mentions: @提及处理 - threading: 线程管理 - reactions: 表情反应 - polls: 投票功能 - meetings: 会议集成 - feedback: 反馈处理 - sso: 单点登录 - deep_links: 深层链接 - incoming_webhook: 入站 Webhook - localization: 本地化 - user_agent: 用户代理 - sent_message_cache: 消息缓存 - types: 类型定义
643 lines
26 KiB
Python
643 lines
26 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from yuxi.channel.protocols import AgentToolParam, MessageActionCapability
|
|
|
|
from .file_consent import FileConsentStore, build_file_consent_attachment
|
|
from .files import UploadRoute, download_public_file, resolve_upload_route
|
|
from .graph import MSTeamsGraphClient
|
|
from .graph_members import add_chat_members, get_chat_members, remove_chat_member
|
|
from .graph_messages import pin_message, search_messages, set_reaction, unpin_message
|
|
from .graph_teams import create_channel, get_joined_teams, get_team_channels, rename_group_chat
|
|
from .outbound import delete_message, edit_message, send_media, send_text
|
|
from .polls import PollStore, build_poll_results_text, send_poll
|
|
from .sdk import BotFrameworkAdapter
|
|
from .sso import SSOTokenStore, build_sso_attachment
|
|
from .types import StoredConversationReference
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_SUPPORTED_ACTIONS = frozenset(
|
|
{
|
|
"send",
|
|
"edit",
|
|
"delete",
|
|
"react",
|
|
"pin",
|
|
"unpin",
|
|
"search_messages",
|
|
"poll_create",
|
|
"poll_results",
|
|
"upload_file",
|
|
"get_members",
|
|
"add_members",
|
|
"remove_member",
|
|
"list_teams",
|
|
"list_channels",
|
|
"create_channel",
|
|
"rename_chat",
|
|
"sso_signin",
|
|
}
|
|
)
|
|
|
|
|
|
class MSTeamsActions:
|
|
def __init__(
|
|
self,
|
|
adapter: BotFrameworkAdapter | None = None,
|
|
store_provider=None,
|
|
poll_store: PollStore | None = None,
|
|
file_consent_store: FileConsentStore | None = None,
|
|
sso_token_store: SSOTokenStore | None = None,
|
|
graph_provider=None,
|
|
):
|
|
self._adapter = adapter
|
|
self._store_provider = store_provider
|
|
self._poll_store = poll_store
|
|
self._file_consent_store = file_consent_store
|
|
self._sso_token_store = sso_token_store
|
|
self._graph_provider = graph_provider
|
|
|
|
def _get_store(self):
|
|
if self._store_provider:
|
|
return self._store_provider()
|
|
return None
|
|
|
|
def _get_graph(self) -> MSTeamsGraphClient | None:
|
|
if self._graph_provider:
|
|
return self._graph_provider()
|
|
return None
|
|
|
|
def get_message_actions(self) -> list[MessageActionCapability]:
|
|
return [
|
|
MessageActionCapability(
|
|
action="send",
|
|
description="发送文本消息到 MS Teams 对话",
|
|
parameters=[
|
|
AgentToolParam(
|
|
name="target_id", type="string", description="会话 ID (conversationId)", required=True
|
|
),
|
|
AgentToolParam(name="content", type="string", description="消息文本内容", required=True),
|
|
AgentToolParam(name="reply_to_id", type="string", description="回复的目标消息 ID", required=False),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="edit",
|
|
description="编辑已发送的消息",
|
|
parameters=[
|
|
AgentToolParam(
|
|
name="target_id", type="string", description="会话 ID (conversationId)", required=True
|
|
),
|
|
AgentToolParam(name="message_id", type="string", description="目标消息 ID", required=True),
|
|
AgentToolParam(name="content", type="string", description="更新后的文本内容", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="delete",
|
|
description="删除已发送的消息",
|
|
parameters=[
|
|
AgentToolParam(
|
|
name="target_id", type="string", description="会话 ID (conversationId)", required=True
|
|
),
|
|
AgentToolParam(name="message_id", type="string", description="目标消息 ID", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="react",
|
|
description="对消息添加反应 (Graph API ChatMessage setReaction)",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
AgentToolParam(name="message_id", type="string", description="目标消息 ID", required=True),
|
|
AgentToolParam(
|
|
name="reaction_type",
|
|
type="string",
|
|
description="反应类型,如 like、heart、laugh、surprised、sad、angry",
|
|
required=True,
|
|
),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="pin",
|
|
description="置顶消息 (Graph API pinnedMessage)",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
AgentToolParam(name="message_id", type="string", description="目标消息 ID", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="unpin",
|
|
description="取消置顶消息",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
AgentToolParam(
|
|
name="pinned_message_id", type="string", description="已置顶消息的 ID", required=True
|
|
),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="search_messages",
|
|
description="搜索消息 (Graph API chat messages search)",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
AgentToolParam(name="query", type="string", description="搜索关键词", required=True),
|
|
AgentToolParam(name="limit", type="integer", description="返回数量上限 (默认20)", required=False),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="poll_create",
|
|
description="创建投票 (支持 Adaptive Card 交互)",
|
|
parameters=[
|
|
AgentToolParam(
|
|
name="target_id", type="string", description="会话 ID (conversationId)", required=True
|
|
),
|
|
AgentToolParam(name="question", type="string", description="投票问题", required=True),
|
|
AgentToolParam(name="options", type="string", description="选项列表,逗号分隔", required=True),
|
|
AgentToolParam(name="is_multi_select", type="boolean", description="是否允许复数", required=False),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="poll_results",
|
|
description="取得投票结果",
|
|
parameters=[
|
|
AgentToolParam(name="poll_id", type="string", description="投票 ID", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="upload_file",
|
|
description="上传文件到 Teams (自动选择最佳路由: <4MB 附件URL / <50MB Connector / >=50MB OneDrive)", # noqa: E501
|
|
parameters=[
|
|
AgentToolParam(
|
|
name="target_id", type="string", description="会话 ID (conversationId)", required=True
|
|
),
|
|
AgentToolParam(name="file_url", type="string", description="文件 URL 或本地路径", required=True),
|
|
AgentToolParam(name="file_name", type="string", description="文件名", required=False),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="get_members",
|
|
description="获取聊天/频道成员列表",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="add_members",
|
|
description="添加成员到群聊",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
AgentToolParam(name="user_ids", type="string", description="用户 ID 列表,逗号分隔", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="remove_member",
|
|
description="从群聊移除成员",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
AgentToolParam(name="membership_id", type="string", description="成员资格 ID", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="list_teams",
|
|
description="列出已加入的团队列表",
|
|
parameters=[],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="list_channels",
|
|
description="列出团队下的频道列表",
|
|
parameters=[
|
|
AgentToolParam(name="team_id", type="string", description="团队 ID", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="create_channel",
|
|
description="在团队中创建新频道",
|
|
parameters=[
|
|
AgentToolParam(name="team_id", type="string", description="团队 ID", required=True),
|
|
AgentToolParam(name="name", type="string", description="频道名称", required=True),
|
|
AgentToolParam(name="description", type="string", description="频道描述", required=False),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="rename_chat",
|
|
description="重命名群聊主题",
|
|
parameters=[
|
|
AgentToolParam(name="chat_id", type="string", description="聊天 ID", required=True),
|
|
AgentToolParam(name="new_topic", type="string", description="新主题名称", required=True),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
MessageActionCapability(
|
|
action="sso_signin",
|
|
description="发起 Microsoft 365 统一登录 (SSO)",
|
|
parameters=[
|
|
AgentToolParam(
|
|
name="target_id", type="string", description="会话 ID (conversationId)", required=True
|
|
),
|
|
AgentToolParam(name="connection_name", type="string", description="OAuth 连接名称", required=False),
|
|
],
|
|
scope="current_channel",
|
|
),
|
|
]
|
|
|
|
async def execute_message_action(self, action: str, params: dict, context: dict) -> dict:
|
|
if action not in _SUPPORTED_ACTIONS:
|
|
return {"success": False, "error": f"不支持的操作: {action}"}
|
|
|
|
try:
|
|
match action:
|
|
case "send":
|
|
return await self._do_send(params, context)
|
|
case "edit":
|
|
return await self._do_edit(params, context)
|
|
case "delete":
|
|
return await self._do_delete(params, context)
|
|
case "react":
|
|
return await self._do_react(params, context)
|
|
case "pin":
|
|
return await self._do_pin(params, context)
|
|
case "unpin":
|
|
return await self._do_unpin(params, context)
|
|
case "search_messages":
|
|
return await self._do_search(params, context)
|
|
case "poll_create":
|
|
return await self._do_poll_create(params, context)
|
|
case "poll_results":
|
|
return await self._do_poll_results(params, context)
|
|
case "upload_file":
|
|
return await self._do_upload_file(params, context)
|
|
case "get_members":
|
|
return await self._do_get_members(params, context)
|
|
case "add_members":
|
|
return await self._do_add_members(params, context)
|
|
case "remove_member":
|
|
return await self._do_remove_member(params, context)
|
|
case "list_teams":
|
|
return await self._do_list_teams(params, context)
|
|
case "list_channels":
|
|
return await self._do_list_channels(params, context)
|
|
case "create_channel":
|
|
return await self._do_create_channel(params, context)
|
|
case "rename_chat":
|
|
return await self._do_rename_chat(params, context)
|
|
case "sso_signin":
|
|
return await self._do_sso_signin(params, context)
|
|
case _:
|
|
return {"success": False, "error": f"操作 {action} 未实现"}
|
|
except Exception as e:
|
|
logger.exception("execute_message_action '%s' failed", action)
|
|
return {"success": False, "error": str(e)}
|
|
|
|
async def _resolve_ref(self, target_id: str) -> StoredConversationReference | None:
|
|
store = self._get_store()
|
|
if not store:
|
|
return None
|
|
return store.get(target_id)
|
|
|
|
async def _do_send(self, params: dict, context: dict) -> dict:
|
|
target_id = params["target_id"]
|
|
content = params["content"]
|
|
reply_to_id = params.get("reply_to_id")
|
|
|
|
ref = await self._resolve_ref(target_id)
|
|
if not ref:
|
|
return {"success": False, "error": f"未找到会话引用: {target_id}"}
|
|
|
|
if not self._adapter:
|
|
return {"success": False, "error": "适配器未初始化"}
|
|
|
|
message_ids = await send_text(self._adapter, ref, content, reply_to_id=reply_to_id)
|
|
return {"success": True, "result": {"message_ids": message_ids}}
|
|
|
|
async def _do_edit(self, params: dict, context: dict) -> dict:
|
|
target_id = params["target_id"]
|
|
message_id = params["message_id"]
|
|
content = params["content"]
|
|
|
|
ref = await self._resolve_ref(target_id)
|
|
if not ref:
|
|
return {"success": False, "error": f"未找到会话引用: {target_id}"}
|
|
|
|
if not self._adapter:
|
|
return {"success": False, "error": "适配器未初始化"}
|
|
|
|
await edit_message(self._adapter, ref, message_id, content)
|
|
return {"success": True, "result": None}
|
|
|
|
async def _do_delete(self, params: dict, context: dict) -> dict:
|
|
target_id = params["target_id"]
|
|
message_id = params["message_id"]
|
|
|
|
ref = await self._resolve_ref(target_id)
|
|
if not ref:
|
|
return {"success": False, "error": f"未找到会话引用: {target_id}"}
|
|
|
|
if not self._adapter:
|
|
return {"success": False, "error": "适配器未初始化"}
|
|
|
|
await delete_message(self._adapter, ref, message_id)
|
|
return {"success": True, "result": None}
|
|
|
|
async def _do_react(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
chat_id = params["chat_id"]
|
|
message_id = params["message_id"]
|
|
reaction_type = params.get("reaction_type", "like")
|
|
|
|
ok = await set_reaction(graph, chat_id, message_id, reaction_type)
|
|
return {"success": ok, "result": None}
|
|
|
|
async def _do_pin(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
ok = await pin_message(graph, params["chat_id"], params["message_id"])
|
|
return {"success": ok, "result": None}
|
|
|
|
async def _do_unpin(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
ok = await unpin_message(graph, params["chat_id"], params["pinned_message_id"])
|
|
return {"success": ok, "result": None}
|
|
|
|
async def _do_search(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
limit = int(params.get("limit", 20))
|
|
msgs = await search_messages(graph, params["chat_id"], params["query"], limit=limit)
|
|
return {"success": True, "result": {"messages": msgs, "count": len(msgs)}}
|
|
|
|
async def _do_poll_create(self, params: dict, context: dict) -> dict:
|
|
target_id = params["target_id"]
|
|
question = params["question"]
|
|
options_raw = params.get("options", "")
|
|
|
|
if isinstance(options_raw, list):
|
|
options = options_raw
|
|
else:
|
|
options = [opt.strip() for opt in str(options_raw).split(",") if opt.strip()]
|
|
|
|
if len(options) < 2:
|
|
return {"success": False, "error": "至少需要两个选项"}
|
|
|
|
is_multi_select = params.get("is_multi_select", False)
|
|
if isinstance(is_multi_select, str):
|
|
is_multi_select = is_multi_select.lower() in ("true", "1", "yes")
|
|
|
|
ref = await self._resolve_ref(target_id)
|
|
if not ref:
|
|
return {"success": False, "error": f"未找到会话引用: {target_id}"}
|
|
|
|
if not self._adapter:
|
|
return {"success": False, "error": "适配器未初始化"}
|
|
|
|
if not self._poll_store:
|
|
return {"success": False, "error": "投票存储未初始化"}
|
|
|
|
poll = await send_poll(
|
|
self._adapter,
|
|
ref,
|
|
question,
|
|
options,
|
|
self._poll_store,
|
|
is_multi_select=is_multi_select,
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"result": {
|
|
"poll_id": poll.poll_id,
|
|
"question": poll.question,
|
|
"options": poll.options,
|
|
"is_multi_select": poll.is_multi_select,
|
|
},
|
|
}
|
|
|
|
async def _do_poll_results(self, params: dict, context: dict) -> dict:
|
|
poll_id = params["poll_id"]
|
|
|
|
if not self._poll_store:
|
|
return {"success": False, "error": "投票存储未初始化"}
|
|
|
|
poll = self._poll_store.get(poll_id)
|
|
if not poll:
|
|
return {"success": False, "error": f"投票不存在或已过期: {poll_id}"}
|
|
|
|
results_text = build_poll_results_text(poll)
|
|
return {
|
|
"success": True,
|
|
"result": {
|
|
"poll_id": poll_id,
|
|
"question": poll.question,
|
|
"results": poll.results,
|
|
"display_text": results_text,
|
|
},
|
|
}
|
|
|
|
async def _do_upload_file(self, params: dict, context: dict) -> dict:
|
|
target_id = params["target_id"]
|
|
file_url = params.get("file_url", "")
|
|
file_name = params.get("file_name", "")
|
|
|
|
ref = await self._resolve_ref(target_id)
|
|
if not ref:
|
|
return {"success": False, "error": f"未找到会话引用: {target_id}"}
|
|
|
|
if not self._adapter:
|
|
return {"success": False, "error": "适配器未初始化"}
|
|
|
|
content = await download_public_file(file_url)
|
|
if not content:
|
|
return {"success": False, "error": f"无法下载文件: {file_url}"}
|
|
|
|
file_name = file_name or file_url.rsplit("/", 1)[-1] or "file"
|
|
route = resolve_upload_route(len(content))
|
|
|
|
match route:
|
|
case UploadRoute.ATTACHMENT_URL:
|
|
message_ids = await send_media(
|
|
self._adapter,
|
|
ref,
|
|
file_url,
|
|
file_name=file_name,
|
|
)
|
|
return {"success": True, "result": {"route": str(route), "message_ids": message_ids}}
|
|
|
|
case UploadRoute.CONNECTOR:
|
|
message_ids = await send_media(
|
|
self._adapter,
|
|
ref,
|
|
file_url,
|
|
file_name=file_name,
|
|
)
|
|
return {"success": True, "result": {"route": str(route), "message_ids": message_ids}}
|
|
|
|
case UploadRoute.ONEDRIVE | UploadRoute.SHAREPOINT:
|
|
if self._file_consent_store and len(content) > 50 * 1024 * 1024:
|
|
consent = self._file_consent_store.create(
|
|
file_name=file_name,
|
|
file_size=len(content),
|
|
conversation_id=ref.conversation_id,
|
|
service_url=ref.service_url,
|
|
tenant_id=ref.tenant_id,
|
|
)
|
|
attachment = build_file_consent_attachment(
|
|
consent.consent_id,
|
|
file_name,
|
|
len(content),
|
|
)
|
|
activity = {"type": "message", "attachments": [attachment]}
|
|
result = await self._adapter.send_to_conversation(
|
|
ref.service_url,
|
|
ref.conversation_id,
|
|
activity,
|
|
)
|
|
return {
|
|
"success": True,
|
|
"result": {
|
|
"route": "file_consent_required",
|
|
"consent_id": consent.consent_id,
|
|
"message_id": result.get("id", ""),
|
|
},
|
|
}
|
|
|
|
return {"success": False, "error": "大文件上传需要配置 Graph API 和 FileConsent"}
|
|
|
|
case _:
|
|
return {"success": False, "error": f"不支持的上传路由: {route}"}
|
|
|
|
async def _do_get_members(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
members = await get_chat_members(graph, params["chat_id"])
|
|
return {"success": True, "result": {"members": members, "count": len(members)}}
|
|
|
|
async def _do_add_members(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
user_ids_raw = params.get("user_ids", "")
|
|
if isinstance(user_ids_raw, list):
|
|
user_ids = user_ids_raw
|
|
else:
|
|
user_ids = [uid.strip() for uid in str(user_ids_raw).split(",") if uid.strip()]
|
|
|
|
ok = await add_chat_members(graph, params["chat_id"], user_ids)
|
|
return {"success": ok, "result": None}
|
|
|
|
async def _do_remove_member(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
ok = await remove_chat_member(graph, params["chat_id"], params["membership_id"])
|
|
return {"success": ok, "result": None}
|
|
|
|
async def _do_list_teams(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
teams = await get_joined_teams(graph)
|
|
return {"success": True, "result": {"teams": teams, "count": len(teams)}}
|
|
|
|
async def _do_list_channels(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
channels = await get_team_channels(graph, params["team_id"])
|
|
return {"success": True, "result": {"channels": channels, "count": len(channels)}}
|
|
|
|
async def _do_create_channel(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
channel = await create_channel(
|
|
graph,
|
|
params["team_id"],
|
|
params["name"],
|
|
description=params.get("description", ""),
|
|
)
|
|
return {"success": channel is not None, "result": channel}
|
|
|
|
async def _do_rename_chat(self, params: dict, context: dict) -> dict:
|
|
graph = self._get_graph()
|
|
if not graph:
|
|
return {"success": False, "error": "Graph API 未配置"}
|
|
|
|
ok = await rename_group_chat(graph, params["chat_id"], params["new_topic"])
|
|
return {"success": ok, "result": None}
|
|
|
|
async def _do_sso_signin(self, params: dict, context: dict) -> dict:
|
|
target_id = params.get("target_id", "")
|
|
connection_name = params.get("connection_name", "MSTeams")
|
|
|
|
ref = await self._resolve_ref(target_id) if target_id else None
|
|
|
|
if not self._sso_token_store:
|
|
return {"success": False, "error": "SSO Token Store 未配置"}
|
|
|
|
state = self._sso_token_store.create_state()
|
|
|
|
attachment = build_sso_attachment(connection_name, state)
|
|
activity: dict = {"type": "message", "attachments": [attachment]}
|
|
|
|
if ref:
|
|
if ref.tenant_id:
|
|
activity.setdefault("channelData", {})
|
|
activity["channelData"]["tenant"] = {"id": ref.tenant_id}
|
|
|
|
if self._adapter:
|
|
await self._adapter.send_to_conversation(
|
|
ref.service_url,
|
|
ref.conversation_id,
|
|
activity,
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"result": {
|
|
"state": state,
|
|
"connection_name": connection_name,
|
|
"message": "SSO 登录卡片已发送",
|
|
},
|
|
}
|
|
|
|
def supports_action(self, action: str) -> bool:
|
|
return action in _SUPPORTED_ACTIONS
|
|
|
|
def resolve_execution_mode(self, action: str) -> str:
|
|
return "async"
|
|
|
|
def requires_trusted_requester_sender(self, action: str, tool_context=None) -> bool:
|
|
return action in ("delete", "remove_member", "add_members")
|