feat: 更新多个文件以使用 SessionMessage 替代 MaiMessage,并调整相关逻辑

This commit is contained in:
DrSmoothl
2026-03-28 13:39:48 +08:00
parent a3bc145051
commit 7a460a474d
15 changed files with 136 additions and 84 deletions

View File

@@ -27,7 +27,7 @@
"startup.main_error": "Main process encountered an exception: {error}",
"startup.opensource_free_notice": " This project is completely free and open-source software, released under the GPL-3.0 license",
"startup.opensource_group": " Official group chat: ",
"startup.opensource_group_value": "1006149251",
"startup.opensource_group_value": "766798517",
"startup.opensource_repo": " Official repository: ",
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
"startup.opensource_resale_warning": " Reselling this software as a \"product\" or concealing its open-source nature violates the license!",

View File

@@ -27,7 +27,7 @@
"startup.main_error": "メインプロセスで例外が発生しました: {error}",
"startup.opensource_free_notice": " 本プロジェクトは完全無料のオープンソースソフトウェアであり、GPL-3.0 ライセンスのもとで公開されています",
"startup.opensource_group": " 公式グループ: ",
"startup.opensource_group_value": "1006149251",
"startup.opensource_group_value": "766798517",
"startup.opensource_repo": " 公式リポジトリ: ",
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
"startup.opensource_resale_warning": " 本ソフトウェアを「商品」として転売したり、オープンソースであることを隠すことはライセンス違反です!",

View File

@@ -27,7 +27,7 @@
"startup.main_error": "메인 프로세스에서 예외 발생: {error}",
"startup.opensource_free_notice": " 본 프로젝트는 완전 무료 오픈소스 소프트웨어이며, GPL-3.0 라이선스로 배포됩니다",
"startup.opensource_group": " 공식 그룹: ",
"startup.opensource_group_value": "1006149251",
"startup.opensource_group_value": "766798517",
"startup.opensource_repo": " 공식 저장소: ",
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
"startup.opensource_resale_warning": " 본 소프트웨어를 '상품'으로 재판매하거나 오픈소스임을 숨기는 행위는 라이선스 위반입니다!",

View File

@@ -27,7 +27,7 @@
"startup.main_error": "主程序发生异常: {error}",
"startup.opensource_free_notice": " 本项目是完全免费的开源软件,基于 GPL-3.0 协议发布",
"startup.opensource_group": " 官方群聊: ",
"startup.opensource_group_value": "1006149251",
"startup.opensource_group_value": "766798517",
"startup.opensource_repo": " 官方仓库: ",
"startup.opensource_repo_value": "https://github.com/MaiM-with-u/MaiBot",
"startup.opensource_resale_warning": " 将本软件作为「商品」倒卖、隐瞒开源性质均违反协议!",

View File

@@ -0,0 +1,55 @@
from datetime import datetime
from pathlib import Path
import sys
from src.chat.message_receive.message import SessionMessage
from src.common.data_models.message_component_data_model import MessageSequence, TextComponent
from src.llm_models.payload_content.tool_option import ToolCall
from src.maisaka.message_adapter import build_message, get_message_kind, get_message_role, get_tool_call_id, get_tool_calls
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
def test_build_message_returns_session_message_with_maisaka_metadata() -> None:
timestamp = datetime.now()
tool_call = ToolCall(
call_id="call-1",
func_name="reply",
args={"message_id": "msg-1"},
)
raw_message = MessageSequence(components=[TextComponent(text="内部消息内容")])
message = build_message(
role="assistant",
content="展示消息内容",
message_kind="perception",
source="assistant",
tool_call_id="call-1",
tool_calls=[tool_call],
timestamp=timestamp,
message_id="maisaka-msg-1",
raw_message=raw_message,
display_text="展示消息内容",
)
assert isinstance(message, SessionMessage)
assert message.initialized is True
assert message.message_id == "maisaka-msg-1"
assert message.timestamp == timestamp
assert message.processed_plain_text == "展示消息内容"
assert message.display_message == "展示消息内容"
assert message.raw_message is raw_message
assert get_message_role(message) == "assistant"
assert get_message_kind(message) == "perception"
assert get_tool_call_id(message) == "call-1"
restored_tool_calls = get_tool_calls(message)
assert len(restored_tool_calls) == 1
assert restored_tool_calls[0].call_id == "call-1"
assert restored_tool_calls[0].func_name == "reply"
assert restored_tool_calls[0].args == {"message_id": "msg-1"}

View File

@@ -343,14 +343,13 @@ class ChatBot:
# message.update_chat_stream(chat)
# 命令处理 - 使用新插件系统检查并处理命令
# 注意:命令返回的 response 当前只用于日志记录和流程判断,
# 不会在这里自动作为回复消息发送回会话。
# is_command, cmd_result, continue_process = await self._process_commands(message)
# 命令处理 - 使用新插件系统检查并处理命令
# 命令处理器内部自行决定是否回复消息,这里只负责流程分发与拦截。
is_command, cmd_result, continue_process = await self._process_commands(message)
# # 如果是命令且不需要继续处理,则直接返回
# if is_command and await self._handle_command_processing_result(message, cmd_result, continue_process):
# return
# 如果是命令且不需要继续处理,则直接返回,避免落入 HeartFlow / MaiSaka。
if is_command and await self._handle_command_processing_result(message, cmd_result, continue_process):
return
# continue_flag, modified_message = await events_manager.handle_mai_events(EventType.ON_MESSAGE, message)
# if not continue_flag:

View File

@@ -328,17 +328,17 @@ class LLMOrchestrator:
start_time = time.time()
if self.request_type.startswith("maisaka_"):
logger.info(
f"LLMOrchestrator[{self.request_type}] generate_response_with_message_async started "
f"LLMOrchestrator[{self.request_type}] 开始执行 generate_response_with_message_async "
f"(temperature={temperature}, max_tokens={max_tokens}, tools={len(tools or [])})"
)
if self.request_type.startswith("maisaka_"):
logger.info(
f"LLMOrchestrator[{self.request_type}] building internal tool options from {len(tools or [])} tool(s)"
f"LLMOrchestrator[{self.request_type}] 正在根据 {len(tools or [])} 个工具构建内部工具选项"
)
tool_built = self._build_tool_options(tools)
if self.request_type.startswith("maisaka_"):
logger.info(f"LLMOrchestrator[{self.request_type}] built {len(tool_built or [])} internal tool option(s)")
logger.info(f"LLMOrchestrator[{self.request_type}] 已构建 {len(tool_built or [])} 个内部工具选项")
execution_result = await self._execute_request(
request_type=RequestType.RESPONSE,
@@ -353,7 +353,7 @@ class LLMOrchestrator:
model_info = execution_result.model_info
if self.request_type.startswith("maisaka_"):
logger.info(
f"LLMOrchestrator[{self.request_type}] generate_response_with_message_async finished "
f"LLMOrchestrator[{self.request_type}] generate_response_with_message_async 执行完成 "
f"(model={model_info.name}, time_cost={time.time() - start_time:.2f}s)"
)
@@ -832,18 +832,18 @@ class LLMOrchestrator:
model_info, api_provider, client = self._select_model(exclude_models=failed_models_this_request)
if self.request_type.startswith("maisaka_"):
logger.info(
f"LLMOrchestrator[{self.request_type}] selected model={model_info.name} "
f"LLMOrchestrator[{self.request_type}] 已选择模型 model={model_info.name} "
f"provider={api_provider.name} request_type={request_type.value}"
)
message_list = []
if message_factory:
if self.request_type.startswith("maisaka_"):
logger.info(f"LLMOrchestrator[{self.request_type}] building message list via message_factory")
logger.info(f"LLMOrchestrator[{self.request_type}] 正在通过 message_factory 构建消息列表")
message_list = message_factory(client)
if self.request_type.startswith("maisaka_"):
logger.info(
f"LLMOrchestrator[{self.request_type}] message_factory returned {len(message_list)} message(s)"
f"LLMOrchestrator[{self.request_type}] message_factory 返回了 {len(message_list)} 条消息"
)
try:
@@ -863,8 +863,8 @@ class LLMOrchestrator:
)
if self.request_type.startswith("maisaka_"):
logger.info(
f"LLMOrchestrator[{self.request_type}] sending request to model={model_info.name} "
f"with tool_options={len(tool_options or [])}"
f"LLMOrchestrator[{self.request_type}] 正在向模型 model={model_info.name} 发送请求 "
f"(tool_options={len(tool_options or [])})"
)
response = await self._attempt_request_on_model(
api_provider,
@@ -873,7 +873,7 @@ class LLMOrchestrator:
)
if self.request_type.startswith("maisaka_"):
logger.info(
f"LLMOrchestrator[{self.request_type}] model={model_info.name} returned API response"
f"LLMOrchestrator[{self.request_type}] 模型 model={model_info.name} 已返回 API 响应"
)
total_tokens, penalty, usage_penalty = self.model_usage[model_info.name]
if response_usage := response.usage:

View File

@@ -13,7 +13,7 @@ from rich.markdown import Markdown
from rich.panel import Panel
from rich.text import Text
from src.common.data_models.mai_message_data_model import MaiMessage
from src.chat.message_receive.message import SessionMessage
from src.config.config import global_config
from .config import (
@@ -26,8 +26,8 @@ from .config import (
from .input_reader import InputReader
from .knowledge import retrieve_relevant_knowledge
from .knowledge_store import get_knowledge_store
from .llm_service import MaiSakaLLMService, build_message, remove_last_perception
from .message_adapter import format_speaker_content
from .llm_service import MaiSakaLLMService
from .message_adapter import build_message, format_speaker_content, remove_last_perception
from .mcp_client import MCPManager
from .tool_handlers import (
ToolHandlerContext,
@@ -47,7 +47,7 @@ class BufferCLI:
def __init__(self):
self.llm_service: Optional[MaiSakaLLMService] = None
self._reader = InputReader()
self._chat_history: Optional[list[MaiMessage]] = None
self._chat_history: Optional[list[SessionMessage]] = None
self._knowledge_store = get_knowledge_store()
knowledge_stats = self._knowledge_store.get_stats()
@@ -122,7 +122,7 @@ class BufferCLI:
await self._run_llm_loop(self._chat_history)
async def _run_llm_loop(self, chat_history: list[MaiMessage]):
async def _run_llm_loop(self, chat_history: list[SessionMessage]):
"""
Main inner loop for the Maisaka planner.
@@ -318,7 +318,7 @@ class BufferCLI:
)
)
async def _generate_visible_reply(self, chat_history: list[MaiMessage], latest_thought: str) -> str:
async def _generate_visible_reply(self, chat_history: list[SessionMessage], latest_thought: str) -> str:
"""Generate and emit a visible reply based on the latest thought."""
if not self.llm_service or not latest_thought:
return ""

View File

@@ -4,7 +4,7 @@ MaiSaka knowledge retrieval helpers.
from typing import List
from src.common.data_models.mai_message_data_model import MaiMessage
from src.chat.message_receive.message import SessionMessage
from .knowledge_store import KNOWLEDGE_CATEGORIES, get_knowledge_store
@@ -43,7 +43,7 @@ def extract_category_ids_from_result(result: str) -> List[str]:
async def retrieve_relevant_knowledge(
llm_service,
chat_history: List[MaiMessage],
chat_history: List[SessionMessage],
) -> str:
"""Retrieve formatted knowledge snippets relevant to the current chat history."""
store = get_knowledge_store()

View File

@@ -19,7 +19,8 @@ from rich.panel import Panel
from rich.pretty import Pretty
from rich.text import Text
from src.common.data_models.mai_message_data_model import MaiMessage
from src.chat.message_receive.message import SessionMessage
from src.common.data_models.llm_service_data_models import LLMGenerationOptions
from src.common.logger import get_logger
from src.common.prompt_i18n import load_prompt
from src.config.config import config_manager, global_config
@@ -31,7 +32,6 @@ from src.llm_models.payload_content.tool_option import (
ToolOption,
normalize_tool_options,
)
from src.common.data_models.llm_service_data_models import LLMGenerationOptions
from src.services.llm_service import LLMServiceClient
from . import config
@@ -55,7 +55,7 @@ class ChatResponse:
content: Optional[str]
tool_calls: List[ToolCall]
raw_message: MaiMessage
raw_message: SessionMessage
class MaiSakaLLMService:
@@ -428,7 +428,7 @@ class MaiSakaLLMService:
padding=(0, 1),
)
async def chat_loop_step(self, chat_history: List[MaiMessage]) -> ChatResponse:
async def chat_loop_step(self, chat_history: List[SessionMessage]) -> ChatResponse:
"""执行主对话循环的一步。
Args:
@@ -514,7 +514,7 @@ class MaiSakaLLMService:
source="assistant",
tool_calls=tool_calls or None,
)
logger.info("已将规划模型响应转换为 MaiMessage")
logger.info("已将规划模型响应转换为 SessionMessage")
return ChatResponse(
content=response,
@@ -522,7 +522,7 @@ class MaiSakaLLMService:
raw_message=raw_message,
)
def _filter_for_api(self, chat_history: List[MaiMessage]) -> str:
def _filter_for_api(self, chat_history: List[SessionMessage]) -> str:
"""将对话历史过滤为简单文本格式。
Args:
@@ -555,14 +555,14 @@ class MaiSakaLLMService:
return "\n\n".join(parts)
def build_chat_context(self, user_text: str) -> List[MaiMessage]:
def build_chat_context(self, user_text: str) -> List[SessionMessage]:
"""构建新的对话上下文。
Args:
user_text: 用户输入文本。
Returns:
List[MaiMessage]: 初始对话上下文消息列表。
List[SessionMessage]: 初始对话上下文消息列表。
"""
return [
build_message(
@@ -572,7 +572,7 @@ class MaiSakaLLMService:
)
]
async def _removed_analyze_timing(self, chat_history: List[MaiMessage], timing_info: str) -> str:
async def _removed_analyze_timing(self, chat_history: List[SessionMessage], timing_info: str) -> str:
"""执行时间节奏分析。
Args:
@@ -623,7 +623,7 @@ class MaiSakaLLMService:
# ──────── 回复生成(使用 replyer 模型) ────────
async def generate_reply(self, reason: str, chat_history: List[MaiMessage]) -> str:
async def generate_reply(self, reason: str, chat_history: List[SessionMessage]) -> str:
"""生成最终回复文本。
Args:

View File

@@ -1,5 +1,5 @@
"""
MaiSaka message adapters built on top of the main project's MaiMessage model.
MaiSaka 内部消息适配器。
"""
from copy import deepcopy
@@ -12,7 +12,8 @@ import re
from PIL import Image as PILImage
from src.common.data_models.mai_message_data_model import GroupInfo, MaiMessage, MessageInfo, UserInfo
from src.chat.message_receive.message import SessionMessage
from src.common.data_models.mai_message_data_model import GroupInfo, MessageInfo, UserInfo
from src.common.data_models.message_component_data_model import EmojiComponent, ImageComponent, MessageSequence, TextComponent
from src.config.config import global_config
from src.llm_models.payload_content.message import Message, MessageBuilder, RoleType
@@ -77,11 +78,11 @@ def build_message(
group_info: Optional[GroupInfo] = None,
raw_message: Optional[MessageSequence] = None,
display_text: Optional[str] = None,
) -> MaiMessage:
"""Build a MaiMessage for the Maisaka session history."""
) -> SessionMessage:
""" MaiSaka 会话历史构建内部 ``SessionMessage``。"""
resolved_timestamp = timestamp or datetime.now()
resolved_role = role.value if isinstance(role, RoleType) else role
message = MaiMessage(
message = SessionMessage(
message_id=message_id or f"maisaka_{uuid4().hex}",
timestamp=resolved_timestamp,
platform=platform,
@@ -104,6 +105,7 @@ def build_message(
visible_text = display_text if display_text is not None else content
message.processed_plain_text = visible_text
message.display_message = visible_text
message.initialized = True
return message
@@ -160,7 +162,7 @@ def _guess_image_format(image_bytes: bytes) -> Optional[str]:
return None
def get_message_text(message: MaiMessage) -> str:
def get_message_text(message: SessionMessage) -> str:
if message.processed_plain_text is not None:
return message.processed_plain_text
if message.display_message is not None:
@@ -174,42 +176,42 @@ def get_message_text(message: MaiMessage) -> str:
return "".join(parts)
def get_message_role(message: MaiMessage) -> str:
def get_message_role(message: SessionMessage) -> str:
return str(message.message_info.additional_config.get(LLM_ROLE_KEY, RoleType.User.value))
def get_message_kind(message: MaiMessage) -> str:
def get_message_kind(message: SessionMessage) -> str:
return str(message.message_info.additional_config.get(MESSAGE_KIND_KEY, "normal"))
def get_message_source(message: MaiMessage) -> str:
def get_message_source(message: SessionMessage) -> str:
return str(message.message_info.additional_config.get(SOURCE_KEY, get_message_role(message)))
def is_perception_message(message: MaiMessage) -> bool:
def is_perception_message(message: SessionMessage) -> bool:
return get_message_kind(message) == "perception"
def get_tool_call_id(message: MaiMessage) -> Optional[str]:
def get_tool_call_id(message: SessionMessage) -> Optional[str]:
value = message.message_info.additional_config.get(TOOL_CALL_ID_KEY)
return str(value) if value else None
def get_tool_calls(message: MaiMessage) -> list[ToolCall]:
def get_tool_calls(message: SessionMessage) -> list[ToolCall]:
raw_tool_calls = message.message_info.additional_config.get(TOOL_CALLS_KEY, [])
if not isinstance(raw_tool_calls, list):
return []
return [_deserialize_tool_call(item) for item in raw_tool_calls if isinstance(item, dict)]
def remove_last_perception(messages: list[MaiMessage]) -> None:
def remove_last_perception(messages: list[SessionMessage]) -> None:
for index in range(len(messages) - 1, -1, -1):
if is_perception_message(messages[index]):
messages.pop(index)
break
def to_llm_message(message: MaiMessage) -> Optional[Message]:
def to_llm_message(message: SessionMessage) -> Optional[Message]:
role = get_message_role(message)
tool_call_id = get_tool_call_id(message)
tool_calls = get_tool_calls(message)

View File

@@ -4,7 +4,7 @@ MaiSaka reply helper.
from typing import Optional
from src.common.data_models.mai_message_data_model import MaiMessage
from src.chat.message_receive.message import SessionMessage
from src.config.config import global_config
from .config import USER_NAME
@@ -19,17 +19,17 @@ def _normalize_content(content: str, limit: int = 500) -> str:
return normalized
def _format_message_time(message: MaiMessage) -> str:
def _format_message_time(message: SessionMessage) -> str:
return message.timestamp.strftime("%H:%M:%S")
def _extract_visible_assistant_reply(message: MaiMessage) -> str:
def _extract_visible_assistant_reply(message: SessionMessage) -> str:
if is_perception_message(message):
return ""
return ""
def _extract_guided_bot_reply(message: MaiMessage) -> str:
def _extract_guided_bot_reply(message: SessionMessage) -> str:
speaker_name, body = parse_speaker_content(get_message_text(message).strip())
bot_nickname = global_config.bot.nickname.strip() or "Bot"
if speaker_name == bot_nickname:
@@ -64,7 +64,7 @@ def _split_user_message_segments(raw_content: str) -> list[tuple[Optional[str],
return segments
def format_chat_history(messages: list[MaiMessage]) -> str:
def format_chat_history(messages: list[SessionMessage]) -> str:
"""Format visible chat history for reply generation."""
bot_nickname = global_config.bot.nickname.strip() or "Bot"
parts: list[str] = []
@@ -109,7 +109,7 @@ class Replyer:
def set_enabled(self, enabled: bool) -> None:
self._enabled = enabled
async def reply(self, reason: str, chat_history: list[MaiMessage]) -> str:
async def reply(self, reason: str, chat_history: list[SessionMessage]) -> str:
if not self._enabled or not reason or self._llm_service is None:
return "..."

View File

@@ -12,7 +12,7 @@ import asyncio
from src.chat.heart_flow.heartFC_utils import CycleDetail
from src.chat.message_receive.chat_manager import BotChatSession, chat_manager
from src.chat.message_receive.message import SessionMessage
from src.common.data_models.mai_message_data_model import GroupInfo, MaiMessage, UserInfo
from src.common.data_models.mai_message_data_model import GroupInfo, UserInfo
from src.common.data_models.message_component_data_model import MessageSequence
from src.common.logger import get_logger
from src.config.config import global_config
@@ -56,7 +56,7 @@ class MaisakaHeartFlowChatting:
session_name = chat_manager.get_session_name(session_id) or session_id
self.log_prefix = f"[{session_name}]"
self._llm_service = MaiSakaLLMService(api_key="", base_url=None, model="")
self._chat_history: list[MaiMessage] = []
self._chat_history: list[SessionMessage] = []
self.history_loop: list[CycleDetail] = []
self.message_cache: list[SessionMessage] = []
self._mcp_manager: Optional[MCPManager] = None
@@ -227,7 +227,7 @@ class MaisakaHeartFlowChatting:
return merged_sequence
async def _build_user_history_message(self, message: SessionMessage) -> Optional[MaiMessage]:
async def _build_user_history_message(self, message: SessionMessage) -> Optional[SessionMessage]:
user_sequence = await self._build_message_sequence(message)
visible_text = build_visible_text_from_sequence(user_sequence).strip()
if not user_sequence.components:
@@ -498,7 +498,7 @@ class MaisakaHeartFlowChatting:
)
return True
def _build_tool_message(self, tool_call: ToolCall, content: str) -> MaiMessage:
def _build_tool_message(self, tool_call: ToolCall, content: str) -> SessionMessage:
return build_message(
role="tool",
content=content,

View File

@@ -11,7 +11,7 @@ import os
from rich.panel import Panel
from src.common.data_models.mai_message_data_model import MaiMessage
from src.chat.message_receive.message import SessionMessage
from src.llm_models.payload_content.tool_option import ToolCall
from .config import console
@@ -41,7 +41,7 @@ class ToolHandlerContext:
self.last_user_input_time: Optional[datetime] = None
async def handle_stop(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
async def handle_stop(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
"""Handle the stop tool."""
console.print("[accent]Calling tool: stop()[/accent]")
chat_history.append(
@@ -49,7 +49,7 @@ async def handle_stop(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
)
async def handle_wait(tc: ToolCall, chat_history: list[MaiMessage], ctx: ToolHandlerContext) -> str:
async def handle_wait(tc: ToolCall, chat_history: list[SessionMessage], ctx: ToolHandlerContext) -> str:
"""Handle the wait tool."""
seconds = (tc.args or {}).get("seconds", 30)
seconds = max(5, min(seconds, 300))
@@ -86,7 +86,7 @@ async def _do_wait(seconds: int, ctx: ToolHandlerContext) -> str:
return f"User input received: {user_input}"
async def handle_mcp_tool(tc: ToolCall, chat_history: list[MaiMessage], mcp_manager: "MCPManager") -> None:
async def handle_mcp_tool(tc: ToolCall, chat_history: list[SessionMessage], mcp_manager: "MCPManager") -> None:
"""Handle an MCP tool call."""
args_str = _json.dumps(tc.args or {}, ensure_ascii=False)
args_preview = args_str if len(args_str) <= 120 else args_str[:120] + "..."
@@ -107,13 +107,13 @@ async def handle_mcp_tool(tc: ToolCall, chat_history: list[MaiMessage], mcp_mana
chat_history.append(build_message(role="tool", content=result, tool_call_id=tc.call_id))
async def handle_unknown_tool(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
async def handle_unknown_tool(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
"""Handle an unknown tool call."""
console.print(f"[accent]Calling unknown tool: {tc.func_name}({tc.args})[/accent]")
chat_history.append(build_message(role="tool", content=f"Unknown tool: {tc.func_name}", tool_call_id=tc.call_id))
async def handle_write_file(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
async def handle_write_file(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
"""Write a file under the local mai_files workspace."""
filename = (tc.args or {}).get("filename", "")
content = (tc.args or {}).get("content", "")
@@ -149,7 +149,7 @@ async def handle_write_file(tc: ToolCall, chat_history: list[MaiMessage]) -> Non
chat_history.append(build_message(role="tool", content=error_msg, tool_call_id=tc.call_id))
async def handle_read_file(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
async def handle_read_file(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
"""Read a file from the local mai_files workspace."""
filename = (tc.args or {}).get("filename", "")
console.print(f'[accent]Calling tool: read_file("{filename}")[/accent]')
@@ -190,7 +190,7 @@ async def handle_read_file(tc: ToolCall, chat_history: list[MaiMessage]) -> None
chat_history.append(build_message(role="tool", content=error_msg, tool_call_id=tc.call_id))
async def handle_list_files(tc: ToolCall, chat_history: list[MaiMessage]) -> None:
async def handle_list_files(tc: ToolCall, chat_history: list[SessionMessage]) -> None:
"""List files under the local mai_files workspace."""
console.print("[accent]Calling tool: list_files()[/accent]")

View File

@@ -139,27 +139,18 @@ class ComponentQueryService:
metadata = dict(entry.metadata)
raw_action_parameters = metadata.get("action_parameters")
action_parameters = (
{
str(param_name): str(param_description)
for param_name, param_description in raw_action_parameters.items()
}
{str(param_name): str(param_description) for param_name, param_description in raw_action_parameters.items()}
if isinstance(raw_action_parameters, dict)
else {}
)
action_require = [
str(item)
for item in (metadata.get("action_require") or [])
if item is not None and str(item).strip()
str(item) for item in (metadata.get("action_require") or []) if item is not None and str(item).strip()
]
associated_types = [
str(item)
for item in (metadata.get("associated_types") or [])
if item is not None and str(item).strip()
str(item) for item in (metadata.get("associated_types") or []) if item is not None and str(item).strip()
]
activation_keywords = [
str(item)
for item in (metadata.get("activation_keywords") or [])
if item is not None and str(item).strip()
str(item) for item in (metadata.get("activation_keywords") or []) if item is not None and str(item).strip()
]
return ActionInfo(
@@ -442,9 +433,14 @@ class ComponentQueryService:
message = kwargs.get("message")
matched_groups = kwargs.get("matched_groups")
plugin_config = kwargs.get("plugin_config")
message_info = getattr(message, "message_info", None)
group_info = getattr(message_info, "group_info", None)
user_info = getattr(message_info, "user_info", None)
invoke_args: Dict[str, Any] = {
"text": str(getattr(message, "processed_plain_text", "") or ""),
"stream_id": str(getattr(message, "session_id", "") or ""),
"group_id": str(getattr(group_info, "group_id", "") or ""),
"user_id": str(getattr(user_info, "user_id", "") or ""),
"matched_groups": matched_groups if isinstance(matched_groups, dict) else {},
}
if isinstance(plugin_config, dict):