@@ -487,13 +487,54 @@ public function extractChatPromptFromRequest($aData)
487487 return '' ;
488488 }
489489
490+ /**
491+ * Guest threads: `g:{clientKey}` (NEO header) or `s:{memberSession}` (UNA cookie).
492+ * Members: profile id. Guest key is kept in session and adopted on login.
493+ */
494+ public function resolveChatHistoryParams ()
495+ {
496+ $ iProfileId = (int )bx_get_logged_profile_id ();
497+ $ oSession = BxDolSession::getInstance ();
498+ $ sHeaderKey = $ this ->readAiGuestKeyFromRequest ();
499+ $ sSessionKey = 'ai_chat_guest_subindex ' ;
500+
501+ if ($ iProfileId ) {
502+ $ sGuest = $ sHeaderKey ? ('g: ' . $ sHeaderKey ) : '' ;
503+ if ($ sGuest === '' ) {
504+ $ sStored = $ oSession ->getValue ($ sSessionKey );
505+ if ($ this ->isAiGuestSubindex ($ sStored ))
506+ $ sGuest = $ sStored ;
507+ }
508+ if ($ sGuest !== '' )
509+ $ this ->_oDb ->adoptGuestChatHistory ($ sGuest , $ iProfileId );
510+ if ($ oSession ->isValue ($ sSessionKey ))
511+ $ oSession ->unsetValue ($ sSessionKey );
512+
513+ return [
514+ 'sender_profile_id ' => $ iProfileId ,
515+ 'chat_history_subindex ' => (string )$ iProfileId ,
516+ ];
517+ }
518+
519+ $ oSession ->start (true );
520+ $ sSessionId = (string )$ oSession ->getId ();
521+ $ sSubindex = $ sHeaderKey ? ('g: ' . $ sHeaderKey ) : ('s: ' . ($ sSessionId !== '' ? $ sSessionId : '0 ' ));
522+ $ oSession ->setValue ($ sSessionKey , $ sSubindex );
523+
524+ return [
525+ 'sender_profile_id ' => 0 ,
526+ 'chat_history_subindex ' => $ sSubindex ,
527+ ];
528+ }
529+
490530 /**
491531 * Transcript for TanStack `useChat` hydrate / `initialMessages`.
492532 * Loads the same NeuronAI chat history the agent uses when streaming.
493533 */
494534 public function getChatHistoryUiMessages ($ iAgentId , $ aParams = [])
495535 {
496- $ aParams ['chat_history_subindex ' ] = (int )($ aParams ['sender_profile_id ' ] ?? 0 );
536+ if (!isset ($ aParams ['chat_history_subindex ' ]))
537+ $ aParams = array_merge ($ this ->resolveChatHistoryParams (), $ aParams );
497538
498539 $ aAgent = BxDolAiQuery::getAgentObject ((int )$ iAgentId );
499540 if (!$ aAgent || empty ($ aAgent ['chat_history_context ' ]))
@@ -567,9 +608,38 @@ protected function neuronMessagesToUiMessages($aMessages)
567608 return $ aResult ;
568609 }
569610
611+ protected function readAiGuestKeyFromRequest ()
612+ {
613+ $ s = $ _SERVER ['HTTP_X_UNA_AI_GUEST_KEY ' ] ?? '' ;
614+ if ($ s === '' && function_exists ('apache_request_headers ' )) {
615+ foreach (apache_request_headers () as $ sName => $ sValue ) {
616+ if (strtolower ((string )$ sName ) === 'x-una-ai-guest-key ' ) {
617+ $ s = $ sValue ;
618+ break ;
619+ }
620+ }
621+ }
622+ return $ this ->sanitizeAiGuestKey ($ s );
623+ }
624+
625+ protected function sanitizeAiGuestKey ($ s )
626+ {
627+ $ s = is_string ($ s ) ? $ s : '' ;
628+ return preg_match ('/^[A-Za-z0-9_-]{16,64}$/ ' , $ s ) ? $ s : '' ;
629+ }
630+
631+ protected function isAiGuestSubindex ($ s )
632+ {
633+ $ s = is_string ($ s ) ? $ s : '' ;
634+ if ($ s === '' || (strncmp ($ s , 'g: ' , 2 ) !== 0 && strncmp ($ s , 's: ' , 2 ) !== 0 ))
635+ return false ;
636+ return $ this ->sanitizeAiGuestKey (substr ($ s , 2 )) !== '' ;
637+ }
638+
570639 public function streamAgentChat ($ iAgentId , $ sPrompt , $ aParams = [], $ sThreadId = null )
571640 {
572- $ aParams ['chat_history_subindex ' ] = (int )($ aParams ['sender_profile_id ' ] ?? 0 );
641+ if (!isset ($ aParams ['chat_history_subindex ' ]))
642+ $ aParams = array_merge ($ this ->resolveChatHistoryParams (), $ aParams );
573643
574644 $ oAdapter = new NeuronAI \Chat \Messages \Stream \Adapters \AGUIAdapter ($ sThreadId );
575645
0 commit comments