feat: 添加 topic_id 支持,确保消息按话题隔离
This commit is contained in:
parent
3a623cc8a3
commit
eeea7d44d0
@ -78,7 +78,7 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
||||
id: response.request_id.to_string(),
|
||||
content: msg.content.clone(),
|
||||
role: "assistant".to_string(),
|
||||
attachments: Vec::new(), subagent_task_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
},
|
||||
MessageKind::Notification => {
|
||||
// 根据元数据判断具体类型
|
||||
@ -98,7 +98,7 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
||||
id: response.request_id.to_string(),
|
||||
content: msg.content.clone(),
|
||||
role: "assistant".to_string(),
|
||||
attachments: Vec::new(), subagent_task_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
},
|
||||
}
|
||||
} else if let Some(session_id) = response.metadata.get("session_id") {
|
||||
@ -137,7 +137,7 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
||||
id: response.request_id.to_string(),
|
||||
content: msg.content.clone(),
|
||||
role: "assistant".to_string(),
|
||||
attachments: Vec::new(), subagent_task_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
},
|
||||
}
|
||||
} else if let Some(sessions_json) = response.metadata.get("sessions") {
|
||||
@ -155,7 +155,7 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
||||
id: response.request_id.to_string(),
|
||||
content: msg.content.clone(),
|
||||
role: "assistant".to_string(),
|
||||
attachments: Vec::new(), subagent_task_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
},
|
||||
}
|
||||
} else if let Some(topics_json) = response.metadata.get("topics") {
|
||||
@ -174,7 +174,7 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
||||
id: response.request_id.to_string(),
|
||||
content: msg.content.clone(),
|
||||
role: "assistant".to_string(),
|
||||
attachments: Vec::new(), subagent_task_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
@ -183,7 +183,7 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
||||
id: response.request_id.to_string(),
|
||||
content: msg.content.clone(),
|
||||
role: "assistant".to_string(),
|
||||
attachments: Vec::new(), subagent_task_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -196,7 +196,7 @@ impl OutputAdapter for WebSocketOutputAdapter {
|
||||
id: response.request_id.to_string(),
|
||||
content: msg.content.clone(),
|
||||
role: "assistant".to_string(),
|
||||
attachments: Vec::new(), subagent_task_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
attachments: Vec::new(), subagent_task_id: None, topic_id: None, timestamp: Some(crate::protocol::now_timestamp()),
|
||||
},
|
||||
};
|
||||
outbounds.push(outbound);
|
||||
|
||||
@ -244,12 +244,17 @@ impl InboundProcessor {
|
||||
}
|
||||
|
||||
// 普通消息进入 AgentLoop
|
||||
// 构建 emitter metadata:包含 forwarded_metadata 和 topic_id(用于前端消息隔离)
|
||||
let mut emitter_metadata = inbound.forwarded_metadata.clone();
|
||||
if let Some(ref topic_id) = current_topic {
|
||||
emitter_metadata.insert("topic_id".to_string(), topic_id.clone());
|
||||
}
|
||||
let live_emitter = Arc::new(PersistingEmittedMessageHandler::new(
|
||||
BusToolCallEmitter::new(
|
||||
self.bus.clone(),
|
||||
inbound.channel.clone(),
|
||||
inbound.chat_id.clone(),
|
||||
inbound.forwarded_metadata.clone(),
|
||||
emitter_metadata,
|
||||
),
|
||||
self.session_manager.store(),
|
||||
&session_id,
|
||||
@ -283,6 +288,10 @@ impl InboundProcessor {
|
||||
Ok(outbound_messages) => {
|
||||
for mut outbound in outbound_messages {
|
||||
outbound.metadata.extend(inbound.forwarded_metadata.clone());
|
||||
// 注入 topic_id 到 outbound metadata,用于前端按话题隔离消息
|
||||
if let Some(ref topic_id) = current_topic {
|
||||
outbound.metadata.insert("topic_id".to_string(), topic_id.clone());
|
||||
}
|
||||
if let Err(error) = self.bus.publish_outbound(outbound).await {
|
||||
tracing::error!(error = %error, "Failed to publish outbound");
|
||||
}
|
||||
|
||||
@ -717,6 +717,7 @@ fn chat_message_to_ws_outbound(msg: &crate::bus::ChatMessage) -> Option<WsOutbou
|
||||
content: format!("{}\nargs: {}", tool_call.name, tool_call.arguments),
|
||||
role: msg.role.clone(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
});
|
||||
}
|
||||
@ -728,6 +729,7 @@ fn chat_message_to_ws_outbound(msg: &crate::bus::ChatMessage) -> Option<WsOutbou
|
||||
role: msg.role.clone(),
|
||||
attachments: Vec::new(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
})
|
||||
}
|
||||
@ -741,6 +743,7 @@ fn chat_message_to_ws_outbound(msg: &crate::bus::ChatMessage) -> Option<WsOutbou
|
||||
content: msg.content.clone(),
|
||||
role: msg.role.clone(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
duration_ms: msg.tool_duration_ms,
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
}),
|
||||
@ -752,6 +755,7 @@ fn chat_message_to_ws_outbound(msg: &crate::bus::ChatMessage) -> Option<WsOutbou
|
||||
role: msg.role.clone(),
|
||||
resume_hint: "完成外部操作后,直接发一条继续消息即可。".to_string(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
}),
|
||||
}
|
||||
@ -762,6 +766,7 @@ fn chat_message_to_ws_outbound(msg: &crate::bus::ChatMessage) -> Option<WsOutbou
|
||||
role: msg.role.clone(),
|
||||
attachments,
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
}),
|
||||
_ => None,
|
||||
|
||||
@ -117,6 +117,8 @@ pub enum WsOutbound {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
subagent_task_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
topic_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
timestamp: Option<i64>,
|
||||
},
|
||||
#[serde(rename = "tool_call")]
|
||||
@ -130,6 +132,8 @@ pub enum WsOutbound {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
subagent_task_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
topic_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
timestamp: Option<i64>,
|
||||
},
|
||||
#[serde(rename = "tool_result")]
|
||||
@ -142,6 +146,8 @@ pub enum WsOutbound {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
subagent_task_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
topic_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
duration_ms: Option<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
timestamp: Option<i64>,
|
||||
@ -157,6 +163,8 @@ pub enum WsOutbound {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
subagent_task_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
topic_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
timestamp: Option<i64>,
|
||||
},
|
||||
#[serde(rename = "error")]
|
||||
|
||||
@ -22,6 +22,8 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
||||
role: message.role.clone(),
|
||||
attachments: Vec::new(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: None,
|
||||
});
|
||||
}
|
||||
|
||||
@ -33,6 +35,8 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
||||
content: format_tool_call_content(&tool_call.name, &tool_call.arguments),
|
||||
role: message.role.clone(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: None,
|
||||
}));
|
||||
outbound
|
||||
} else {
|
||||
@ -42,6 +46,8 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
||||
role: message.role.clone(),
|
||||
attachments: Vec::new(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
@ -57,7 +63,9 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
||||
content: message.content.clone(),
|
||||
role: message.role.clone(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
duration_ms: None,
|
||||
timestamp: None,
|
||||
}],
|
||||
ToolMessageState::PendingUserAction => vec![WsOutbound::ToolPending {
|
||||
id: message.id.clone(),
|
||||
@ -67,6 +75,8 @@ pub(crate) fn ws_outbound_from_chat_message(message: &ChatMessage) -> Vec<WsOutb
|
||||
role: message.role.clone(),
|
||||
resume_hint: TOOL_PENDING_RESUME_HINT.to_string(),
|
||||
subagent_task_id: None,
|
||||
topic_id: None,
|
||||
timestamp: None,
|
||||
}],
|
||||
},
|
||||
_ => Vec::new(),
|
||||
@ -93,6 +103,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve
|
||||
role: message.role.clone(),
|
||||
attachments,
|
||||
subagent_task_id: message.metadata.get("subagent_task_id").cloned(),
|
||||
topic_id: message.metadata.get("topic_id").cloned(),
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
}]
|
||||
}
|
||||
@ -110,6 +121,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve
|
||||
content: message.content.clone(),
|
||||
role: message.role.clone(),
|
||||
subagent_task_id: message.metadata.get("subagent_task_id").cloned(),
|
||||
topic_id: message.metadata.get("topic_id").cloned(),
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
}],
|
||||
OutboundEventKind::ToolResult => vec![WsOutbound::ToolResult {
|
||||
@ -122,6 +134,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve
|
||||
content: message.content.clone(),
|
||||
role: message.role.clone(),
|
||||
subagent_task_id: message.metadata.get("subagent_task_id").cloned(),
|
||||
topic_id: message.metadata.get("topic_id").cloned(),
|
||||
duration_ms: message
|
||||
.metadata
|
||||
.get("tool_duration_ms")
|
||||
@ -139,6 +152,7 @@ pub(crate) fn ws_outbound_from_outbound_message(message: &OutboundMessage) -> Ve
|
||||
role: message.role.clone(),
|
||||
resume_hint: TOOL_PENDING_RESUME_HINT.to_string(),
|
||||
subagent_task_id: message.metadata.get("subagent_task_id").cloned(),
|
||||
topic_id: message.metadata.get("topic_id").cloned(),
|
||||
timestamp: Some(crate::protocol::now_timestamp()),
|
||||
}],
|
||||
OutboundEventKind::ErrorNotification => vec![WsOutbound::Error {
|
||||
|
||||
@ -392,6 +392,8 @@ export function useChat(): UseChatReturn {
|
||||
|
||||
case 'assistant_response': {
|
||||
const msg = message as AssistantResponse
|
||||
// 按 topic_id 隔离:如果消息属于其他话题则丢弃
|
||||
if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) return
|
||||
const role = msg.role === 'user' || msg.role === 'tool' ? msg.role : 'assistant'
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
@ -416,6 +418,8 @@ export function useChat(): UseChatReturn {
|
||||
|
||||
case 'tool_call': {
|
||||
const msg = message as ToolCall
|
||||
// 按 topic_id 隔离:如果消息属于其他话题则丢弃
|
||||
if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) return
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
@ -435,6 +439,8 @@ export function useChat(): UseChatReturn {
|
||||
|
||||
case 'tool_result': {
|
||||
const msg = message as ToolResult
|
||||
// 按 topic_id 隔离:如果消息属于其他话题则丢弃
|
||||
if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) return
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
@ -454,6 +460,8 @@ export function useChat(): UseChatReturn {
|
||||
|
||||
case 'tool_pending': {
|
||||
const msg = message as ToolPending
|
||||
// 按 topic_id 隔离:如果消息属于其他话题则丢弃
|
||||
if (msg.topic_id && msg.topic_id !== selectedTopicRef.current) return
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
|
||||
@ -43,6 +43,7 @@ export interface AssistantResponse {
|
||||
role: string
|
||||
attachments?: Attachment[]
|
||||
subagent_task_id?: string
|
||||
topic_id?: string
|
||||
timestamp?: number
|
||||
}
|
||||
|
||||
@ -55,6 +56,7 @@ export interface ToolCall {
|
||||
content: string
|
||||
role: string
|
||||
subagent_task_id?: string
|
||||
topic_id?: string
|
||||
timestamp?: number
|
||||
}
|
||||
|
||||
@ -66,6 +68,7 @@ export interface ToolResult {
|
||||
content: string
|
||||
role: string
|
||||
subagent_task_id?: string
|
||||
topic_id?: string
|
||||
duration_ms?: number
|
||||
timestamp?: number
|
||||
}
|
||||
@ -79,6 +82,7 @@ export interface ToolPending {
|
||||
role: string
|
||||
resume_hint: string
|
||||
subagent_task_id?: string
|
||||
topic_id?: string
|
||||
timestamp?: number
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user