1313import langbot_plugin .api .entities .builtin .platform .entities as platform_entities
1414import langbot_plugin .api .definition .abstract .platform .event_logger as abstract_platform_logger
1515from ...core import app
16- from .websocket_manager import ws_connection_manager , WebSocketConnection
16+ from .websocket_manager import WebSocketConnection , is_valid_session_id , ws_connection_manager
1717
1818logger = logging .getLogger (__name__ )
1919
@@ -91,6 +91,59 @@ def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventL
9191 self .outbound_message_queue = asyncio .Queue ()
9292 self .stream_enabled = True
9393
94+ @staticmethod
95+ def _conversation_key (pipeline_uuid : str , session_id : str | None = None ) -> str :
96+ """Return the history key for a pipeline/client conversation."""
97+ return f'{ pipeline_uuid } :{ session_id } ' if session_id else pipeline_uuid
98+
99+ @staticmethod
100+ def _parse_embed_target (target_id : str ) -> tuple [str , str ] | None :
101+ """Extract pipeline and session identifiers from a stable embed launcher."""
102+ target_value = str (target_id )
103+ for prefix in ('websocket_' , 'websocketgroup_' ):
104+ if target_value .startswith (prefix ):
105+ target = target_value [len (prefix ) :]
106+ break
107+ else :
108+ return None
109+ if ':' not in target :
110+ return None
111+ pipeline_uuid , session_id = target .rsplit (':' , 1 )
112+ if not pipeline_uuid or not is_valid_session_id (session_id ):
113+ return None
114+ return pipeline_uuid , session_id
115+
116+ @classmethod
117+ async def _get_connection_from_target (cls , target_id : str ):
118+ """Resolve a person or group WebSocket launcher to its connection."""
119+ target_value = str (target_id )
120+ for prefix in ('websocket_' , 'websocketgroup_' ):
121+ if target_value .startswith (prefix ):
122+ target = target_value [len (prefix ) :]
123+ break
124+ else :
125+ return None
126+ connection = await ws_connection_manager .get_connection (target )
127+ if connection is not None :
128+ return connection
129+ embed_target = cls ._parse_embed_target (target_id )
130+ if embed_target is not None :
131+ pipeline_uuid , session_id = embed_target
132+ return await ws_connection_manager .get_connection_by_session_id (session_id , pipeline_uuid )
133+ return await ws_connection_manager .get_connection_by_session_id (target )
134+
135+ async def _get_message_context (self , message_source ) -> tuple [str , str | None ]:
136+ """Resolve the originating pipeline and browser session for a reply."""
137+ sender = getattr (message_source , 'sender' , None )
138+ sender_id = getattr (sender , 'id' , '' )
139+ connection = await self ._get_connection_from_target (sender_id )
140+ if connection is not None :
141+ return connection .pipeline_uuid , connection .session_id
142+ embed_target = self ._parse_embed_target (sender_id )
143+ if embed_target is not None :
144+ return embed_target
145+ return typing .cast (str , self .ap .platform_mgr .websocket_proxy_bot .bot_entity .use_pipeline_uuid ), None
146+
94147 async def send_message (
95148 self ,
96149 target_type : str ,
@@ -103,15 +156,26 @@ async def send_message(
103156 target_id 可能是 launcher_id(如 websocket_xxx)或 pipeline_uuid。
104157 我们需要尝试两种方式来确保消息能够送达。
105158 """
106- # 获取当前的 pipeline_uuid
107- pipeline_uuid = self .ap .platform_mgr .websocket_proxy_bot .bot_entity .use_pipeline_uuid
159+ connection = await self ._get_connection_from_target (target_id )
160+ if connection is not None :
161+ pipeline_uuid = connection .pipeline_uuid
162+ session_id = connection .session_id
163+ else :
164+ embed_target = self ._parse_embed_target (target_id )
165+ if embed_target is not None :
166+ pipeline_uuid , session_id = embed_target
167+ else :
168+ pipeline_uuid = typing .cast (
169+ str ,
170+ self .ap .platform_mgr .websocket_proxy_bot .bot_entity .use_pipeline_uuid ,
171+ )
172+ session_id = None
108173 session_type = 'group' if target_type == 'group' else 'person'
174+ conversation_key = self ._conversation_key (pipeline_uuid , session_id )
109175
110- # 选择会话
111176 session = self .websocket_group_session if session_type == 'group' else self .websocket_person_session
112177
113- # 生成唯一消息ID
114- msg_id = len (session .get_message_list (pipeline_uuid )) + 1
178+ msg_id = len (session .get_message_list (conversation_key )) + 1
115179
116180 message_data = WebSocketMessage (
117181 id = msg_id ,
@@ -122,10 +186,8 @@ async def send_message(
122186 is_final = True ,
123187 )
124188
125- # 保存到历史记录
126- session .get_message_list (pipeline_uuid ).append (message_data )
189+ session .get_message_list (conversation_key ).append (message_data )
127190
128- # 直接广播到当前pipeline的连接
129191 await ws_connection_manager .broadcast_to_pipeline (
130192 pipeline_uuid ,
131193 {
@@ -134,6 +196,7 @@ async def send_message(
134196 'data' : message_data .model_dump (),
135197 },
136198 session_type = session_type ,
199+ session_id = session_id ,
137200 )
138201
139202 return message_data .model_dump ()
@@ -152,12 +215,11 @@ async def reply_message(
152215 else self .websocket_person_session
153216 )
154217
155- # 从message_source获取pipeline_uuid和connection_id
156- pipeline_uuid = self .ap .platform_mgr .websocket_proxy_bot .bot_entity .use_pipeline_uuid
218+ pipeline_uuid , session_id = await self ._get_message_context (message_source )
157219 session_type = 'group' if isinstance (message_source , platform_events .GroupMessage ) else 'person'
220+ conversation_key = self ._conversation_key (pipeline_uuid , session_id )
158221
159- # 生成新的消息ID
160- msg_id = len (session .get_message_list (pipeline_uuid )) + 1
222+ msg_id = len (session .get_message_list (conversation_key )) + 1
161223
162224 message_data = WebSocketMessage (
163225 id = msg_id ,
@@ -168,10 +230,8 @@ async def reply_message(
168230 is_final = True ,
169231 )
170232
171- # 保存到历史记录
172- session .get_message_list (pipeline_uuid ).append (message_data )
233+ session .get_message_list (conversation_key ).append (message_data )
173234
174- # 直接广播到所有该pipeline的连接,包含session_type信息
175235 await ws_connection_manager .broadcast_to_pipeline (
176236 pipeline_uuid ,
177237 {
@@ -180,6 +240,7 @@ async def reply_message(
180240 'data' : message_data .model_dump (),
181241 },
182242 session_type = session_type ,
243+ session_id = session_id ,
183244 )
184245
185246 return message_data .model_dump ()
@@ -200,10 +261,11 @@ async def reply_message_chunk(
200261 else self .websocket_person_session
201262 )
202263
203- pipeline_uuid = self .ap . platform_mgr . websocket_proxy_bot . bot_entity . use_pipeline_uuid
264+ pipeline_uuid , session_id = await self ._get_message_context ( message_source )
204265 session_type = 'group' if isinstance (message_source , platform_events .GroupMessage ) else 'person'
205- message_list = session .get_message_list (pipeline_uuid )
206- stream_message_indexes = session .get_stream_message_indexes (pipeline_uuid )
266+ conversation_key = self ._conversation_key (pipeline_uuid , session_id )
267+ message_list = session .get_message_list (conversation_key )
268+ stream_message_indexes = session .get_stream_message_indexes (conversation_key )
207269
208270 # Streaming messages in LangBot have a stable resp_message_id during the same assistant reply.
209271 # Use it as the primary key to avoid overwriting an old card from a previous reply.
@@ -247,7 +309,6 @@ async def reply_message_chunk(
247309 if message_is_final and resp_message_id :
248310 stream_message_indexes .pop (resp_message_id , None )
249311
250- # 直接广播到所有该pipeline的连接,包含session_type信息
251312 await ws_connection_manager .broadcast_to_pipeline (
252313 pipeline_uuid ,
253314 {
@@ -256,6 +317,7 @@ async def reply_message_chunk(
256317 'data' : message_data .model_dump (),
257318 },
258319 session_type = session_type ,
320+ session_id = session_id ,
259321 )
260322
261323 return message_data .model_dump ()
@@ -381,23 +443,19 @@ async def handle_websocket_message(
381443 """
382444 pipeline_uuid = connection .pipeline_uuid
383445 session_type = connection .session_type
446+ conversation_key = self ._conversation_key (pipeline_uuid , connection .session_id )
384447
385- # 获取stream参数,默认为True
386448 self .stream_enabled = message_data .get ('stream' , True )
387449
388- # 选择会话
389450 use_session = self .websocket_group_session if session_type == 'group' else self .websocket_person_session
390451
391- # 解析消息链
392452 message_chain_obj = message_data .get ('message' , [])
393453
394- # 处理图片组件:将path转换为base64
395454 await self ._process_image_components (message_chain_obj )
396455
397456 message_chain = platform_message .MessageChain .model_validate (message_chain_obj )
398457
399- # 生成消息ID
400- message_id = len (use_session .get_message_list (pipeline_uuid )) + 1
458+ message_id = len (use_session .get_message_list (conversation_key )) + 1
401459
402460 # 保存用户消息
403461 user_message = WebSocketMessage (
@@ -409,9 +467,8 @@ async def handle_websocket_message(
409467 connection_id = connection .connection_id ,
410468 is_final = True , # 用户消息始终是完整的,非流式
411469 )
412- use_session .get_message_list (pipeline_uuid ).append (user_message )
470+ use_session .get_message_list (conversation_key ).append (user_message )
413471
414- # 广播用户消息到所有连接(包括发送者),包含session_type信息
415472 await ws_connection_manager .broadcast_to_pipeline (
416473 pipeline_uuid ,
417474 {
@@ -420,25 +477,27 @@ async def handle_websocket_message(
420477 'data' : user_message .model_dump (),
421478 },
422479 session_type = session_type ,
480+ session_id = connection .session_id ,
423481 )
424482
425483 # 添加消息源
426484 message_chain .insert (0 , platform_message .Source (id = message_id , time = datetime .now ().timestamp ()))
427485
428486 # 创建事件
487+ launcher_id = f'{ pipeline_uuid } :{ connection .session_id } ' if connection .session_id else connection .connection_id
429488 if session_type == 'person' :
430- sender = platform_entities .Friend (
431- id = f'websocket_{ connection .connection_id } ' , nickname = 'User' , remark = 'User'
432- )
489+ sender = platform_entities .Friend (id = f'websocket_{ launcher_id } ' , nickname = 'User' , remark = 'User' )
433490 event = platform_events .FriendMessage (
434491 sender = sender , message_chain = message_chain , time = datetime .now ().timestamp ()
435492 )
436493 else :
437494 group = platform_entities .Group (
438- id = 'websocketgroup' , name = 'Group' , permission = platform_entities .Permission .Member
495+ id = f'websocketgroup_{ launcher_id } ' if connection .session_id else 'websocketgroup' ,
496+ name = 'Group' ,
497+ permission = platform_entities .Permission .Member ,
439498 )
440499 sender = platform_entities .GroupMember (
441- id = f'websocket_{ connection . connection_id } ' ,
500+ id = f'websocket_{ launcher_id } ' ,
442501 member_name = 'User' ,
443502 group = group ,
444503 permission = platform_entities .Permission .Member ,
@@ -468,22 +527,27 @@ async def handle_websocket_message(
468527 if event .__class__ in listeners :
469528 asyncio .create_task (listeners [event .__class__ ](event , callback_adapter ))
470529
471- def get_websocket_messages (self , pipeline_uuid : str , session_type : str ) -> list [dict ]:
472- """获取消息历史"""
473- if session_type == 'person' :
474- return [message .model_dump () for message in self .websocket_person_session .get_message_list (pipeline_uuid )]
475- else :
476- return [message .model_dump () for message in self .websocket_group_session .get_message_list (pipeline_uuid )]
477-
478- def reset_session (self , pipeline_uuid : str , session_type : str ):
479- """重置会话"""
480- if session_type == 'person' :
481- if pipeline_uuid in self .websocket_person_session .message_lists :
482- self .websocket_person_session .message_lists [pipeline_uuid ] = []
483- if pipeline_uuid in self .websocket_person_session .stream_message_indexes :
484- self .websocket_person_session .stream_message_indexes [pipeline_uuid ] = {}
485- else :
486- if pipeline_uuid in self .websocket_group_session .message_lists :
487- self .websocket_group_session .message_lists [pipeline_uuid ] = []
488- if pipeline_uuid in self .websocket_group_session .stream_message_indexes :
489- self .websocket_group_session .stream_message_indexes [pipeline_uuid ] = {}
530+ def get_websocket_messages (
531+ self ,
532+ pipeline_uuid : str ,
533+ session_type : str ,
534+ session_id : str | None = None ,
535+ ) -> list [dict ]:
536+ """Return history for one pipeline/client conversation."""
537+ conversation_key = self ._conversation_key (pipeline_uuid , session_id )
538+ session = self .websocket_person_session if session_type == 'person' else self .websocket_group_session
539+ return [message .model_dump () for message in session .get_message_list (conversation_key )]
540+
541+ def reset_session (
542+ self ,
543+ pipeline_uuid : str ,
544+ session_type : str ,
545+ session_id : str | None = None ,
546+ ):
547+ """Reset one pipeline/client conversation."""
548+ conversation_key = self ._conversation_key (pipeline_uuid , session_id )
549+ session = self .websocket_person_session if session_type == 'person' else self .websocket_group_session
550+ if conversation_key in session .message_lists :
551+ session .message_lists [conversation_key ] = []
552+ if conversation_key in session .stream_message_indexes :
553+ session .stream_message_indexes [conversation_key ] = {}
0 commit comments