feat: 添加 session_id 支持到 OutboundMessage,优化会话管理

This commit is contained in:
oudecheng 2026-05-15 10:00:17 +08:00
parent 0095ace411
commit 025c0b5d7f
8 changed files with 44 additions and 17 deletions

View File

@ -251,7 +251,12 @@ impl InboundMessage {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OutboundMessage { pub struct OutboundMessage {
pub channel: String, pub channel: String,
/// 消息发送目标 ID如飞书 open_id、微信 chat_id
/// 注意:这始终是原始入站消息的 chat_id不会被修改为会话 ID
pub chat_id: String, pub chat_id: String,
/// 内部会话 ID对应 sessions.id
/// 用于会话管理和消息持久化,与消息发送目标无关
pub session_id: Option<String>,
pub content: String, pub content: String,
pub reply_to: Option<String>, pub reply_to: Option<String>,
pub media: Vec<MediaItem>, pub media: Vec<MediaItem>,
@ -281,6 +286,7 @@ impl OutboundMessage {
pub fn assistant( pub fn assistant(
channel: impl Into<String>, channel: impl Into<String>,
chat_id: impl Into<String>, chat_id: impl Into<String>,
session_id: Option<String>,
content: impl Into<String>, content: impl Into<String>,
reply_to: Option<String>, reply_to: Option<String>,
metadata: HashMap<String, String>, metadata: HashMap<String, String>,
@ -288,6 +294,7 @@ impl OutboundMessage {
Self { Self {
channel: channel.into(), channel: channel.into(),
chat_id: chat_id.into(), chat_id: chat_id.into(),
session_id,
content: content.into(), content: content.into(),
reply_to, reply_to,
media: Vec::new(), media: Vec::new(),
@ -303,11 +310,12 @@ impl OutboundMessage {
pub fn scheduler_notification( pub fn scheduler_notification(
channel: impl Into<String>, channel: impl Into<String>,
chat_id: impl Into<String>, chat_id: impl Into<String>,
session_id: Option<String>,
content: impl Into<String>, content: impl Into<String>,
reply_to: Option<String>, reply_to: Option<String>,
metadata: HashMap<String, String>, metadata: HashMap<String, String>,
) -> Self { ) -> Self {
let mut message = Self::assistant(channel, chat_id, content, reply_to, metadata); let mut message = Self::assistant(channel, chat_id, session_id, content, reply_to, metadata);
message.event_kind = OutboundEventKind::SchedulerNotification; message.event_kind = OutboundEventKind::SchedulerNotification;
message message
} }
@ -315,11 +323,12 @@ impl OutboundMessage {
pub fn error_notification( pub fn error_notification(
channel: impl Into<String>, channel: impl Into<String>,
chat_id: impl Into<String>, chat_id: impl Into<String>,
session_id: Option<String>,
content: impl Into<String>, content: impl Into<String>,
reply_to: Option<String>, reply_to: Option<String>,
metadata: HashMap<String, String>, metadata: HashMap<String, String>,
) -> Self { ) -> Self {
let mut message = Self::assistant(channel, chat_id, content, reply_to, metadata); let mut message = Self::assistant(channel, chat_id, session_id, content, reply_to, metadata);
message.event_kind = OutboundEventKind::ErrorNotification; message.event_kind = OutboundEventKind::ErrorNotification;
message message
} }
@ -327,6 +336,7 @@ impl OutboundMessage {
pub fn tool_call( pub fn tool_call(
channel: impl Into<String>, channel: impl Into<String>,
chat_id: impl Into<String>, chat_id: impl Into<String>,
session_id: Option<String>,
message_id: impl Into<String>, message_id: impl Into<String>,
tool_name: impl Into<String>, tool_name: impl Into<String>,
tool_arguments: serde_json::Value, tool_arguments: serde_json::Value,
@ -338,6 +348,7 @@ impl OutboundMessage {
Self { Self {
channel: channel.into(), channel: channel.into(),
chat_id: chat_id.into(), chat_id: chat_id.into(),
session_id,
content, content,
reply_to, reply_to,
media: Vec::new(), media: Vec::new(),
@ -353,6 +364,7 @@ impl OutboundMessage {
pub fn tool_result( pub fn tool_result(
channel: impl Into<String>, channel: impl Into<String>,
chat_id: impl Into<String>, chat_id: impl Into<String>,
session_id: Option<String>,
tool_call_id: impl Into<String>, tool_call_id: impl Into<String>,
tool_name: impl Into<String>, tool_name: impl Into<String>,
content: impl Into<String>, content: impl Into<String>,
@ -365,6 +377,7 @@ impl OutboundMessage {
Self { Self {
channel: channel.into(), channel: channel.into(),
chat_id: chat_id.into(), chat_id: chat_id.into(),
session_id,
content, content,
reply_to, reply_to,
media: Vec::new(), media: Vec::new(),
@ -380,6 +393,7 @@ impl OutboundMessage {
pub fn tool_pending( pub fn tool_pending(
channel: impl Into<String>, channel: impl Into<String>,
chat_id: impl Into<String>, chat_id: impl Into<String>,
session_id: Option<String>,
tool_call_id: impl Into<String>, tool_call_id: impl Into<String>,
tool_name: impl Into<String>, tool_name: impl Into<String>,
content: impl Into<String>, content: impl Into<String>,
@ -392,6 +406,7 @@ impl OutboundMessage {
Self { Self {
channel: channel.into(), channel: channel.into(),
chat_id: chat_id.into(), chat_id: chat_id.into(),
session_id,
content, content,
reply_to, reply_to,
media: Vec::new(), media: Vec::new(),
@ -407,6 +422,7 @@ impl OutboundMessage {
pub fn from_chat_message( pub fn from_chat_message(
channel: &str, channel: &str,
chat_id: &str, chat_id: &str,
session_id: Option<String>,
reply_to: Option<String>, reply_to: Option<String>,
metadata: &HashMap<String, String>, metadata: &HashMap<String, String>,
message: &ChatMessage, message: &ChatMessage,
@ -419,6 +435,7 @@ impl OutboundMessage {
outbound.push(Self::assistant( outbound.push(Self::assistant(
channel.to_string(), channel.to_string(),
chat_id.to_string(), chat_id.to_string(),
session_id.clone(),
message.content.clone(), message.content.clone(),
reply_to.clone(), reply_to.clone(),
metadata.clone(), metadata.clone(),
@ -429,6 +446,7 @@ impl OutboundMessage {
Self::tool_call( Self::tool_call(
channel.to_string(), channel.to_string(),
chat_id.to_string(), chat_id.to_string(),
session_id.clone(),
tool_call.id.clone(), tool_call.id.clone(),
tool_call.name.clone(), tool_call.name.clone(),
tool_call.arguments.clone(), tool_call.arguments.clone(),
@ -441,6 +459,7 @@ impl OutboundMessage {
vec![Self::assistant( vec![Self::assistant(
channel.to_string(), channel.to_string(),
chat_id.to_string(), chat_id.to_string(),
session_id,
message.content.clone(), message.content.clone(),
reply_to, reply_to,
metadata.clone(), metadata.clone(),
@ -455,6 +474,7 @@ impl OutboundMessage {
ToolMessageState::Completed => vec![Self::tool_result( ToolMessageState::Completed => vec![Self::tool_result(
channel.to_string(), channel.to_string(),
chat_id.to_string(), chat_id.to_string(),
session_id,
message.tool_call_id.clone().unwrap_or_default(), message.tool_call_id.clone().unwrap_or_default(),
message.tool_name.clone().unwrap_or_default(), message.tool_name.clone().unwrap_or_default(),
message.content.clone(), message.content.clone(),
@ -464,6 +484,7 @@ impl OutboundMessage {
ToolMessageState::PendingUserAction => vec![Self::tool_pending( ToolMessageState::PendingUserAction => vec![Self::tool_pending(
channel.to_string(), channel.to_string(),
chat_id.to_string(), chat_id.to_string(),
session_id,
message.tool_call_id.clone().unwrap_or_default(), message.tool_call_id.clone().unwrap_or_default(),
message.tool_name.clone().unwrap_or_default(), message.tool_name.clone().unwrap_or_default(),
message.content.clone(), message.content.clone(),
@ -562,6 +583,7 @@ mod tests {
TEST_CHANNEL, TEST_CHANNEL,
"chat-1", "chat-1",
None, None,
None,
&HashMap::new(), &HashMap::new(),
&message, &message,
); );
@ -599,6 +621,7 @@ mod tests {
TEST_CHANNEL, TEST_CHANNEL,
"chat-1", "chat-1",
None, None,
None,
&HashMap::new(), &HashMap::new(),
&message, &message,
); );
@ -618,6 +641,7 @@ mod tests {
TEST_CHANNEL, TEST_CHANNEL,
"chat-1", "chat-1",
None, None,
None,
&HashMap::new(), &HashMap::new(),
&message, &message,
); );
@ -639,6 +663,7 @@ mod tests {
TEST_CHANNEL, TEST_CHANNEL,
"chat-1", "chat-1",
None, None,
None,
&HashMap::new(), &HashMap::new(),
&message, &message,
); );

View File

@ -119,6 +119,7 @@ mod tests {
.send(OutboundMessage::assistant( .send(OutboundMessage::assistant(
"cli", "cli",
"session-1", "session-1",
None, // session_id
"hello", "hello",
None, None,
HashMap::new(), HashMap::new(),
@ -143,6 +144,7 @@ mod tests {
.send(OutboundMessage::assistant( .send(OutboundMessage::assistant(
"cli", "cli",
"session-1", "session-1",
None, // session_id
"hello", "hello",
None, None,
HashMap::new(), HashMap::new(),

View File

@ -114,6 +114,7 @@ impl AgentExecutionService {
OutboundMessage::from_chat_message( OutboundMessage::from_chat_message(
request.channel_name, request.channel_name,
request.chat_id, request.chat_id,
None, // session_id
None, None,
request.metadata, request.metadata,
message, message,

View File

@ -125,32 +125,23 @@ impl InboundProcessor {
let cmd_ctx = crate::command::context::CommandContext::new(&inbound.channel, &inbound.channel) let cmd_ctx = crate::command::context::CommandContext::new(&inbound.channel, &inbound.channel)
.with_session_id(&inbound.chat_id); .with_session_id(&inbound.chat_id);
// 记录是否是创建会话命令(用于后续自动切换 // 记录是否是创建会话命令(用于后续处理
let is_create_session = matches!(cmd, Command::CreateSession { .. }); let _is_create_session = matches!(cmd, Command::CreateSession { .. });
let response = self.command_router.dispatch_with_response(cmd, cmd_ctx).await; let response = self.command_router.dispatch_with_response(cmd, cmd_ctx).await;
// 发送响应给用户 // 发送响应给用户
if response.success { if response.success {
// 如果是创建会话,更新 chat_id 到新会话
let target_chat_id = if let Some(session_id) = response.metadata.get("session_id") {
if is_create_session {
// 自动切换到新会话
session_id.clone()
} else {
inbound.chat_id.clone()
}
} else {
inbound.chat_id.clone()
};
// 提取响应消息 // 提取响应消息
// chat_id 保持为 inbound.chat_id飞书 open_id
// session_id 放入 metadata 用于会话管理
for msg in &response.messages { for msg in &response.messages {
if let Err(error) = self if let Err(error) = self
.bus .bus
.publish_outbound(OutboundMessage::assistant( .publish_outbound(OutboundMessage::assistant(
inbound.channel.clone(), inbound.channel.clone(),
target_chat_id.clone(), inbound.chat_id.clone(),
response.metadata.get("session_id").cloned(),
msg.content.clone(), msg.content.clone(),
None, None,
inbound.forwarded_metadata.clone(), inbound.forwarded_metadata.clone(),
@ -166,6 +157,7 @@ impl InboundProcessor {
.publish_outbound(OutboundMessage::assistant( .publish_outbound(OutboundMessage::assistant(
inbound.channel.clone(), inbound.channel.clone(),
inbound.chat_id.clone(), inbound.chat_id.clone(),
response.metadata.get("session_id").cloned(),
format!("Error [{}]: {}", error.code, error.message), format!("Error [{}]: {}", error.code, error.message),
None, None,
inbound.forwarded_metadata.clone(), inbound.forwarded_metadata.clone(),
@ -216,6 +208,7 @@ impl InboundProcessor {
.publish_outbound(OutboundMessage::error_notification( .publish_outbound(OutboundMessage::error_notification(
inbound.channel, inbound.channel,
inbound.chat_id, inbound.chat_id,
None, // session_id
error.to_string(), error.to_string(),
None, None,
metadata, metadata,

View File

@ -79,6 +79,7 @@ impl EmittedMessageHandler for BusToolCallEmitter {
for outbound in OutboundMessage::from_chat_message( for outbound in OutboundMessage::from_chat_message(
&self.channel_name, &self.channel_name,
&self.chat_id, &self.chat_id,
None, // session_id
None, None,
&self.metadata, &self.metadata,
&message, &message,

View File

@ -47,6 +47,7 @@ impl SessionMessageSender for BusSessionMessageSender {
.publish_outbound(OutboundMessage::assistant( .publish_outbound(OutboundMessage::assistant(
channel_name.to_string(), channel_name.to_string(),
chat_id.to_string(), chat_id.to_string(),
None, // session_id
text, text,
None, None,
metadata.clone(), metadata.clone(),
@ -68,6 +69,7 @@ impl SessionMessageSender for BusSessionMessageSender {
let mut outbound = OutboundMessage::assistant( let mut outbound = OutboundMessage::assistant(
channel_name.to_string(), channel_name.to_string(),
chat_id.to_string(), chat_id.to_string(),
None, // session_id
String::new(), String::new(),
None, None,
metadata.clone(), metadata.clone(),

View File

@ -201,6 +201,7 @@ mod tests {
let message = OutboundMessage::tool_call( let message = OutboundMessage::tool_call(
"cli", "cli",
"session-1", "session-1",
None, // session_id
"call-1", "call-1",
"calculator", "calculator",
json!({"expression": "1 + 1"}), json!({"expression": "1 + 1"}),

View File

@ -409,6 +409,7 @@ impl Scheduler {
.publish_outbound(OutboundMessage::error_notification( .publish_outbound(OutboundMessage::error_notification(
channel, channel,
chat_id, chat_id,
None, // session_id
format!( format!(
"定时任务执行失败:{}\n{}", "定时任务执行失败:{}\n{}",
job.id, job.id,
@ -904,6 +905,7 @@ fn build_outbound_message(job: &RuntimeJob) -> anyhow::Result<OutboundMessage> {
Ok(OutboundMessage::scheduler_notification( Ok(OutboundMessage::scheduler_notification(
channel, channel,
chat_id, chat_id,
None, // session_id
content.to_string(), content.to_string(),
job.target.reply_to.clone(), job.target.reply_to.clone(),
metadata, metadata,