Просмотр исходного кода

#5406 AI Agents revamp: ACL levels for agents

Aleksandr Trofimov 1 месяц назад
Родитель
Сommit
8baa8dd439

+ 38 - 3
inc/classes/BxDolAI.php

@@ -393,7 +393,7 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
         // set additional params
         // set additional params
         $aParams = [];
         $aParams = [];
         if ('message' == $sType || 'form-input' == $sType || 'alert' == $sType) {
         if ('message' == $sType || 'form-input' == $sType || 'alert' == $sType) {
-            $aParams = ['chat_history_subindex' => $mixedParams['sender_profile_id']];
+            $aParams = ['chat_history_subindex' => (int)($mixedParams['sender_profile_id'] ?? 0)];
         }
         }
 
 
         // call agent
         // call agent
@@ -415,6 +415,41 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
         return $mixed;
         return $mixed;
     }
     }
 
 
+    /**
+     * Whether the current (or given) member may interact with the agent
+     * via chat, messenger, or form-input. Empty acl_levels means Nobody.
+     * Site admins / Studio operators always may, so they can debug.
+     */
+    public function canInteract($aAgent, $iProfileId = false, $bAllowOperators = true)
+    {
+        if (!is_array($aAgent) || empty($aAgent['id']))
+            return false;
+
+        if ($bAllowOperators && isAdmin())
+            return true;
+
+        $iLevels = (int)($aAgent['acl_levels'] ?? 0);
+        if (!$iLevels)
+            return false;
+
+        return (bool)BxDolAcl::getInstance()->isMemberLevelInSet($iLevels, $iProfileId);
+    }
+
+    /**
+     * Direct HTTP chat (`sys-ai-chat`). Operators may talk to any trigger;
+     * everyone else only `manual` and `message` agents they can interact with.
+     */
+    public function canChatDirectly($aAgent, $iProfileId = false)
+    {
+        if (!$this->canInteract($aAgent, $iProfileId))
+            return false;
+
+        if (isAdmin())
+            return true;
+
+        return in_array($aAgent['trigger'] ?? '', ['manual', 'message'], true);
+    }
+
     public function extractChatPromptFromRequest($aData)
     public function extractChatPromptFromRequest($aData)
     {
     {
         if (!is_array($aData))
         if (!is_array($aData))
@@ -458,7 +493,7 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
      */
      */
     public function getChatHistoryUiMessages($iAgentId, $aParams = [])
     public function getChatHistoryUiMessages($iAgentId, $aParams = [])
     {
     {
-        $aParams['chat_history_subindex'] = $aParams['sender_profile_id'] ?? 0;
+        $aParams['chat_history_subindex'] = (int)($aParams['sender_profile_id'] ?? 0);
 
 
         $aAgent = BxDolAiQuery::getAgentObject((int)$iAgentId);
         $aAgent = BxDolAiQuery::getAgentObject((int)$iAgentId);
         if (!$aAgent || empty($aAgent['chat_history_context']))
         if (!$aAgent || empty($aAgent['chat_history_context']))
@@ -534,7 +569,7 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
 
 
     public function streamAgentChat($iAgentId, $sPrompt, $aParams = [], $sThreadId = null)
     public function streamAgentChat($iAgentId, $sPrompt, $aParams = [], $sThreadId = null)
     {
     {
-        $aParams['chat_history_subindex'] = $aParams['sender_profile_id'] ?? 0;
+        $aParams['chat_history_subindex'] = (int)($aParams['sender_profile_id'] ?? 0);
 
 
         $oAdapter = new NeuronAI\Chat\Messages\Stream\Adapters\AGUIAdapter($sThreadId);
         $oAdapter = new NeuronAI\Chat\Messages\Stream\Adapters\AGUIAdapter($sThreadId);
 
 

+ 2 - 0
inc/classes/BxDolAiAlertResponse.php

@@ -29,6 +29,8 @@ class BxDolAiAlertResponse extends BxDolAlertsResponse
         if($aAgents = $oAi->getAgentsByProfileId($iRecipient)) {
         if($aAgents = $oAi->getAgentsByProfileId($iRecipient)) {
             $GLOBALS['glAgentsCallQueue'] = [];
             $GLOBALS['glAgentsCallQueue'] = [];
             foreach($aAgents as $a) {
             foreach($aAgents as $a) {
+                if (!$oAi->canInteract($a, $iSender))
+                    continue;
                 if (!$a['message_profile_id'] || $iSender == $a['message_profile_id']) {
                 if (!$a['message_profile_id'] || $iSender == $a['message_profile_id']) {
 
 
                     $aParams = [
                     $aParams = [

+ 1 - 0
install/sql/system.sql

@@ -7238,6 +7238,7 @@ CREATE TABLE `sys_agents_agents` (
   `icon` text NOT NULL,
   `icon` text NOT NULL,
   `model_id` int(11) NOT NULL DEFAULT 0,
   `model_id` int(11) NOT NULL DEFAULT 0,
   `profile_id` int(11) NOT NULL DEFAULT 0,
   `profile_id` int(11) NOT NULL DEFAULT 0,
+  `acl_levels` int(10) UNSIGNED NOT NULL DEFAULT 0,
   `prompt_system` text NOT NULL,
   `prompt_system` text NOT NULL,
   `prompt_steps` text NOT NULL,
   `prompt_steps` text NOT NULL,
   `prompt_output` text NOT NULL,
   `prompt_output` text NOT NULL,

+ 4 - 0
modules/boonex/english/data/langs/system/en.xml

@@ -2296,6 +2296,8 @@
     <string name="_sys_agents_form_input_info"><![CDATA[Form input where AI-generated text should be pasted]]></string>
     <string name="_sys_agents_form_input_info"><![CDATA[Form input where AI-generated text should be pasted]]></string>
     <string name="_sys_agents_agent_form_input_popup_text"><![CDATA[Please describe what you want to insert into this field.]]></string>
     <string name="_sys_agents_agent_form_input_popup_text"><![CDATA[Please describe what you want to insert into this field.]]></string>
     <string name="_sys_agents_json_field_err"><![CDATA[Invalid JSON format.]]></string>
     <string name="_sys_agents_json_field_err"><![CDATA[Invalid JSON format.]]></string>
+    <string name="_sys_agents_unauthorized"><![CDATA[You are not allowed to interact with this agent.]]></string>
+    <string name="_sys_agents_agent_not_found"><![CDATA[Agent was not found or is inactive.]]></string>
     <string name="_sys_agents_automators_txt_active"><![CDATA[Active]]></string>
     <string name="_sys_agents_automators_txt_active"><![CDATA[Active]]></string>
     <string name="_sys_agents_automators_txt_name"><![CDATA[Name]]></string>
     <string name="_sys_agents_automators_txt_name"><![CDATA[Name]]></string>
     <string name="_sys_agents_automators_txt_type"><![CDATA[Type]]></string>
     <string name="_sys_agents_automators_txt_type"><![CDATA[Type]]></string>
@@ -2336,6 +2338,8 @@
     <string name="_sys_agents_automators_field_profile_id_inf"><![CDATA[Leave empty to use default.]]></string>
     <string name="_sys_agents_automators_field_profile_id_inf"><![CDATA[Leave empty to use default.]]></string>
     <string name="_sys_agents_field_profile_id"><![CDATA[Profile]]></string>
     <string name="_sys_agents_field_profile_id"><![CDATA[Profile]]></string>
     <string name="_sys_agents_field_profile_id_inf"><![CDATA[AI agent will perform actions under this profile]]></string>
     <string name="_sys_agents_field_profile_id_inf"><![CDATA[AI agent will perform actions under this profile]]></string>
+    <string name="_sys_agents_field_acl_levels"><![CDATA[Who can interact]]></string>
+    <string name="_sys_agents_field_acl_levels_info"><![CDATA[Membership levels that may chat with this agent, use it in messenger, or fill form fields with it. Leave empty for Nobody. Studio operators can always use the agent to debug. Unauthenticated allows guests.]]></string>
     <string name="_sys_agents_automators_field_type"><![CDATA[Type]]></string>
     <string name="_sys_agents_automators_field_type"><![CDATA[Type]]></string>
     <string name="_sys_agents_automators_field_type_event"><![CDATA[Event]]></string>
     <string name="_sys_agents_automators_field_type_event"><![CDATA[Event]]></string>
     <string name="_sys_agents_automators_field_type_scheduler"><![CDATA[Scheduler]]></string>
     <string name="_sys_agents_automators_field_type_scheduler"><![CDATA[Scheduler]]></string>

+ 4 - 0
modules/boonex/russian/data/langs/system/ru.xml

@@ -2270,6 +2270,8 @@
         <string name="_sys_agents_form_input_info"><![CDATA[Поле формы, в которое нужно вставить текст, сгенерированный ИИ]]></string>
         <string name="_sys_agents_form_input_info"><![CDATA[Поле формы, в которое нужно вставить текст, сгенерированный ИИ]]></string>
         <string name="_sys_agents_agent_form_input_popup_text"><![CDATA[Опишите, что вы хотите вставить в это поле.]]></string>
         <string name="_sys_agents_agent_form_input_popup_text"><![CDATA[Опишите, что вы хотите вставить в это поле.]]></string>
         <string name="_sys_agents_json_field_err"><![CDATA[Неверный формат JSON.]]></string>
         <string name="_sys_agents_json_field_err"><![CDATA[Неверный формат JSON.]]></string>
+        <string name="_sys_agents_unauthorized"><![CDATA[Вам не разрешено взаимодействовать с этим агентом.]]></string>
+        <string name="_sys_agents_agent_not_found"><![CDATA[Агент не найден или неактивен.]]></string>
         <string name="_sys_agents_automators_txt_active"><![CDATA[Активный]]></string>
         <string name="_sys_agents_automators_txt_active"><![CDATA[Активный]]></string>
         <string name="_sys_agents_automators_txt_name"><![CDATA[Название]]></string>
         <string name="_sys_agents_automators_txt_name"><![CDATA[Название]]></string>
         <string name="_sys_agents_automators_txt_type"><![CDATA[Тип]]></string>
         <string name="_sys_agents_automators_txt_type"><![CDATA[Тип]]></string>
@@ -2308,6 +2310,8 @@
         <string name="_sys_agents_automators_field_profile_id_inf"><![CDATA[Оставьте пустым, чтобы использовать значение по умолчанию.]]></string>
         <string name="_sys_agents_automators_field_profile_id_inf"><![CDATA[Оставьте пустым, чтобы использовать значение по умолчанию.]]></string>
         <string name="_sys_agents_field_profile_id"><![CDATA[Профиль]]></string>
         <string name="_sys_agents_field_profile_id"><![CDATA[Профиль]]></string>
         <string name="_sys_agents_field_profile_id_inf"><![CDATA[Профайл, от имени которого ИИ агент выполняет действия.]]></string>
         <string name="_sys_agents_field_profile_id_inf"><![CDATA[Профайл, от имени которого ИИ агент выполняет действия.]]></string>
+        <string name="_sys_agents_field_acl_levels"><![CDATA[Кто может взаимодействовать]]></string>
+        <string name="_sys_agents_field_acl_levels_info"><![CDATA[Уровни членства, которые могут общаться с этим агентом, писать ему в мессенджере или заполнять поля форм с его помощью. Оставьте пустым — никто. Операторы Studio всегда могут использовать агента для отладки. «Неаутентифицированный» разрешает гостям.]]></string>
         <string name="_sys_agents_automators_field_type"><![CDATA[Тип]]></string>
         <string name="_sys_agents_automators_field_type"><![CDATA[Тип]]></string>
         <string name="_sys_agents_automators_field_type_event"><![CDATA[Событие]]></string>
         <string name="_sys_agents_automators_field_type_event"><![CDATA[Событие]]></string>
         <string name="_sys_agents_automators_field_type_scheduler"><![CDATA[Планировщик]]></string>
         <string name="_sys_agents_automators_field_type_scheduler"><![CDATA[Планировщик]]></string>

+ 15 - 0
studio/template/scripts/BxBaseStudioAgentsAgents.php

@@ -135,6 +135,7 @@ class BxBaseStudioAgentsAgents extends BxDolStudioAgentsAgents
 
 
             $sTools = is_array($oForm->getCleanValue('tools')) ? implode(',', $oForm->getCleanValue('tools')) : '';
             $sTools = is_array($oForm->getCleanValue('tools')) ? implode(',', $oForm->getCleanValue('tools')) : '';
             $aValsToAdd['tools'] = $sTools;
             $aValsToAdd['tools'] = $sTools;
+            $aValsToAdd['acl_levels'] = (int)$oForm->getCleanValue('acl_levels');
 
 
             $bIsValid = true;
             $bIsValid = true;
             if($bIsValid) {
             if($bIsValid) {
@@ -203,6 +204,7 @@ class BxBaseStudioAgentsAgents extends BxDolStudioAgentsAgents
 
 
             $sTools = is_array($oForm->getCleanValue('tools')) ? implode(',', $oForm->getCleanValue('tools')) : '';
             $sTools = is_array($oForm->getCleanValue('tools')) ? implode(',', $oForm->getCleanValue('tools')) : '';
             $aValsToAdd['tools'] = $sTools;
             $aValsToAdd['tools'] = $sTools;
+            $aValsToAdd['acl_levels'] = (int)$oForm->getCleanValue('acl_levels');
 
 
             $bIsValid = true;
             $bIsValid = true;
             if($bIsValid) {
             if($bIsValid) {
@@ -469,6 +471,17 @@ class BxBaseStudioAgentsAgents extends BxDolStudioAgentsAgents
                         'pass' => 'Int',
                         'pass' => 'Int',
                     ]
                     ]
                 ],
                 ],
+                'acl_levels' => [
+                    'type' => 'checkbox_set',
+                    'name' => 'acl_levels',
+                    'caption' => _t('_sys_agents_field_acl_levels'),
+                    'info' => _t('_sys_agents_field_acl_levels_info'),
+                    'value' => [],
+                    'values' => [],
+                    'db' => [
+                        'pass' => 'Set',
+                    ]
+                ],
                 'trigger' => [
                 'trigger' => [
                     'type' => 'select',
                     'type' => 'select',
                     'name' => 'trigger',
                     'name' => 'trigger',
@@ -744,6 +757,8 @@ class BxBaseStudioAgentsAgents extends BxDolStudioAgentsAgents
 
 
         $this->_getMultiField('tools', $aAgent, 'getTools', 'toolAdd', 'agents_agents_form_tools.html', $aForm);
         $this->_getMultiField('tools', $aAgent, 'getTools', 'toolAdd', 'agents_agents_form_tools.html', $aForm);
 
 
+        BxDolStudioUtils::getVisibilityValues((int)($aAgent['acl_levels'] ?? 0), $aForm['inputs']['acl_levels']['values'], $aForm['inputs']['acl_levels']['value']);
+
         return $aForm;
         return $aForm;
     }
     }
 
 

+ 7 - 1
template/scripts/BxBaseFormView.php

@@ -154,7 +154,13 @@ class BxBaseFormView extends BxDolForm
         );
         );
 
 
         $this->_sAgentFormObject = $aInfo['params']['object'] ?? $this->id;
         $this->_sAgentFormObject = $aInfo['params']['object'] ?? $this->id;
-        $this->_aAgentsFormObject = BxDolAI::getInstance()->getAgentsByFormObject($this->_sAgentFormObject);
+        $oAi = BxDolAI::getInstance();
+        $this->_aAgentsFormObject = $oAi ? $oAi->getAgentsByFormObject($this->_sAgentFormObject) : [];
+        if ($this->_aAgentsFormObject && is_array($this->_aAgentsFormObject)) {
+            $this->_aAgentsFormObject = array_filter($this->_aAgentsFormObject, function ($a) use ($oAi) {
+                return $oAi->canInteract($a);
+            });
+        }
     }
     }
 
 
     public function performActionGetHelp()
     public function performActionGetHelp()

+ 41 - 31
template/scripts/BxBaseServices.php

@@ -1761,9 +1761,6 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
 
 
     public function serviceCallAgentForFormInput($iAgentId)
     public function serviceCallAgentForFormInput($iAgentId)
     {
     {
-        if (!bx_get_logged_profile_id())
-            return echoJson(['code' => 403, 'msg' => _t('_sys_agents_unauthorized')]);
-
         $sJson = file_get_contents('php://input');
         $sJson = file_get_contents('php://input');
         $aData = json_decode($sJson, true);
         $aData = json_decode($sJson, true);
 
 
@@ -1775,8 +1772,12 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
         $aValues = $aData['values'] ?? [];
         $aValues = $aData['values'] ?? [];
 
 
         $aAgent = BxDolAiQuery::getAgentObject($iAgentId);
         $aAgent = BxDolAiQuery::getAgentObject($iAgentId);
-        if (!$aAgent || !$aAgent['active'])
+        if (!$aAgent || !$aAgent['active'] || $aAgent['trigger'] !== 'form-input')
             return echoJson(['code' => 404, 'msg' => _t('_sys_agents_agent_not_found')]);
             return echoJson(['code' => 404, 'msg' => _t('_sys_agents_agent_not_found')]);
+
+        $oAi = BxDolAI::getInstance();
+        if (!$oAi || !$oAi->canInteract($aAgent))
+            return echoJson(['code' => 403, 'msg' => _t('_sys_agents_unauthorized')]);
         
         
         $aParams = [
         $aParams = [
             'sender_profile_id' => bx_get_logged_profile_id(),
             'sender_profile_id' => bx_get_logged_profile_id(),
@@ -1785,7 +1786,6 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
             'form_values' => $aValues,
             'form_values' => $aValues,
         ];
         ];
 
 
-        $oAi = BxDolAI::getInstance();
         $mixed = $oAi->callAgent('form-input', $aAgent, $aParams);
         $mixed = $oAi->callAgent('form-input', $aAgent, $aParams);
 
 
         return echoJson(['code' => 200, 'msg' => $mixed]);
         return echoJson(['code' => 200, 'msg' => $mixed]);
@@ -1793,10 +1793,6 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
 
 
     public function serviceAiChat($iAgentId)
     public function serviceAiChat($iAgentId)
     {
     {
-        // TODO: tmp
-        // if (!bx_get_logged_profile_id())
-        //    return echoJson(['code' => 403, 'msg' => _t('_sys_agents_unauthorized')]);
-
         $sMethod = $_SERVER['REQUEST_METHOD'] ?? '';
         $sMethod = $_SERVER['REQUEST_METHOD'] ?? '';
         if ('GET' === $sMethod)
         if ('GET' === $sMethod)
             return $this->_aiChatHydrate($iAgentId);
             return $this->_aiChatHydrate($iAgentId);
@@ -1804,13 +1800,9 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
         if ('POST' !== $sMethod)
         if ('POST' !== $sMethod)
             return echoJson(['code' => 405, 'msg' => _t('_error occured')]);
             return echoJson(['code' => 405, 'msg' => _t('_error occured')]);
 
 
-        $iAgentId = (int)$iAgentId;
-        if (!$iAgentId)
-            return echoJson(['code' => 400, 'msg' => _t('_sys_agents_agent_not_found')]);
-
-        $aAgent = BxDolAiQuery::getAgentObject($iAgentId);
-        if (!$aAgent || !$aAgent['active'])
-            return echoJson(['code' => 404, 'msg' => _t('_sys_agents_agent_not_found')]);
+        $aAgent = $this->_aiChatLoadAgent($iAgentId);
+        if (!$aAgent)
+            return;
 
 
         $sJson = file_get_contents('php://input');
         $sJson = file_get_contents('php://input');
         $aData = json_decode($sJson, true);
         $aData = json_decode($sJson, true);
@@ -1818,14 +1810,11 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
             return echoJson(['code' => 400, 'msg' => _t('_sys_agents_json_field_err')]);
             return echoJson(['code' => 400, 'msg' => _t('_sys_agents_json_field_err')]);
 
 
         $oAi = BxDolAI::getInstance();
         $oAi = BxDolAI::getInstance();
-        if (!$oAi)
-            return echoJson(['code' => 503, 'msg' => _t('_sys_agents_exception')]);
-
         $sPrompt = $oAi->extractChatPromptFromRequest($aData);
         $sPrompt = $oAi->extractChatPromptFromRequest($aData);
         if (!$sPrompt)
         if (!$sPrompt)
             return echoJson(['code' => 400, 'msg' => _t('_sys_agents_json_field_err')]);
             return echoJson(['code' => 400, 'msg' => _t('_sys_agents_json_field_err')]);
 
 
-        $oAi->streamAgentChat($iAgentId, $sPrompt, [
+        $oAi->streamAgentChat($aAgent['id'], $sPrompt, [
             'sender_profile_id' => bx_get_logged_profile_id(),
             'sender_profile_id' => bx_get_logged_profile_id(),
         ], $aData['threadId'] ?? null);
         ], $aData['threadId'] ?? null);
     }
     }
@@ -1839,20 +1828,13 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
         if (false !== bx_get('offset'))
         if (false !== bx_get('offset'))
             return echoJson(['code' => 405, 'msg' => _t('_error occured')]);
             return echoJson(['code' => 405, 'msg' => _t('_error occured')]);
 
 
-        $iAgentId = (int)$iAgentId;
-        if (!$iAgentId)
-            return echoJson(['code' => 400, 'msg' => _t('_sys_agents_agent_not_found')]);
-
-        $aAgent = BxDolAiQuery::getAgentObject($iAgentId);
-        if (!$aAgent || !$aAgent['active'])
-            return echoJson(['code' => 404, 'msg' => _t('_sys_agents_agent_not_found')]);
+        $aAgent = $this->_aiChatLoadAgent($iAgentId);
+        if (!$aAgent)
+            return;
 
 
         $oAi = BxDolAI::getInstance();
         $oAi = BxDolAI::getInstance();
-        if (!$oAi)
-            return echoJson(['code' => 503, 'msg' => _t('_sys_agents_exception')]);
-
         return echoJson([
         return echoJson([
-            'messages' => $oAi->getChatHistoryUiMessages($iAgentId, [
+            'messages' => $oAi->getChatHistoryUiMessages($aAgent['id'], [
                 'sender_profile_id' => bx_get_logged_profile_id(),
                 'sender_profile_id' => bx_get_logged_profile_id(),
             ]),
             ]),
             'activeRun' => null,
             'activeRun' => null,
@@ -1860,6 +1842,34 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
         ]);
         ]);
     }
     }
 
 
+    protected function _aiChatLoadAgent($iAgentId)
+    {
+        $iAgentId = (int)$iAgentId;
+        if (!$iAgentId) {
+            echoJson(['code' => 400, 'msg' => _t('_sys_agents_agent_not_found')]);
+            return false;
+        }
+
+        $aAgent = BxDolAiQuery::getAgentObject($iAgentId);
+        if (!$aAgent || !$aAgent['active']) {
+            echoJson(['code' => 404, 'msg' => _t('_sys_agents_agent_not_found')]);
+            return false;
+        }
+
+        $oAi = BxDolAI::getInstance();
+        if (!$oAi) {
+            echoJson(['code' => 503, 'msg' => _t('_sys_agents_exception')]);
+            return false;
+        }
+
+        if (!$oAi->canChatDirectly($aAgent)) {
+            echoJson(['code' => 403, 'msg' => _t('_sys_agents_unauthorized')]);
+            return false;
+        }
+
+        return $aAgent;
+    }
+
     protected function _getOptionsAiAssistants()
     protected function _getOptionsAiAssistants()
     {
     {
         $aResult = ['' => _t('_Select_one')];
         $aResult = ['' => _t('_Select_one')];