2025-04-16 05:42:56 +08:00
import os
2025-03-30 14:48:57 +08:00
import json
2025-07-09 03:32:33 +08:00
import asyncio
2025-08-11 20:33:10 +08:00
import datetime
2025-07-30 13:50:05 +08:00
from typing import Optional , Dict , List , Any , AsyncGenerator
from dataclasses import dataclass
2025-04-10 18:52:56 +08:00
2025-04-18 06:17:48 +08:00
from agent . tools . message_tool import MessageTool
2025-04-13 05:40:01 +08:00
from agent . tools . sb_deploy_tool import SandboxDeployTool
2025-04-21 22:01:51 +08:00
from agent . tools . sb_expose_tool import SandboxExposeTool
2025-05-10 11:46:48 +08:00
from agent . tools . web_search_tool import SandboxWebSearchTool
2025-04-10 18:52:56 +08:00
from dotenv import load_dotenv
2025-04-24 08:45:58 +08:00
from utils . config import config
2025-05-31 23:31:20 +08:00
from agent . agent_builder_prompt import get_agent_builder_prompt
2025-04-06 17:10:18 +08:00
from agentpress . thread_manager import ThreadManager
2025-04-10 18:52:56 +08:00
from agentpress . response_processor import ProcessorConfig
2025-04-09 07:37:06 +08:00
from agent . tools . sb_shell_tool import SandboxShellTool
2025-04-09 20:54:25 +08:00
from agent . tools . sb_files_tool import SandboxFilesTool
2025-04-17 01:23:28 +08:00
from agent . tools . data_providers_tool import DataProvidersTool
2025-05-29 21:47:31 +08:00
from agent . tools . expand_msg_tool import ExpandMessageTool
2025-04-07 00:45:02 +08:00
from agent . prompt import get_system_prompt
2025-08-17 10:55:17 +08:00
2025-05-10 12:37:07 +08:00
from utils . logger import logger
2025-04-27 07:47:06 +08:00
from utils . auth_utils import get_account_id_from_thread
2025-08-20 07:36:28 +08:00
from services . billing import check_billing_status
2025-04-28 08:28:21 +08:00
from agent . tools . sb_vision_tool import SandboxVisionTool
2025-06-09 09:38:50 +08:00
from agent . tools . sb_image_edit_tool import SandboxImageEditTool
2025-08-06 05:12:46 +08:00
from agent . tools . sb_presentation_outline_tool import SandboxPresentationOutlineTool
2025-08-21 14:06:01 +08:00
from agent . tools . sb_presentation_tool import SandboxPresentationTool
2025-05-21 08:39:28 +08:00
from services . langfuse import langfuse
from langfuse . client import StatefulTraceClient
2025-08-17 10:55:17 +08:00
2025-05-25 02:17:55 +08:00
from agent . tools . mcp_tool_wrapper import MCPToolWrapper
2025-07-30 00:27:02 +08:00
from agent . tools . task_list_tool import TaskListTool
2025-05-30 15:31:11 +08:00
from agentpress . tool import SchemaType
2025-08-08 14:08:49 +08:00
from agent . tools . sb_sheets_tool import SandboxSheetsTool
2025-08-09 23:24:53 +08:00
from agent . tools . sb_web_dev_tool import SandboxWebDevTool
2025-08-21 19:19:27 +08:00
from agent . tools . sb_upload_file_tool import SandboxUploadFileTool
2025-04-11 00:02:21 +08:00
2025-04-04 23:06:49 +08:00
load_dotenv ( )
2025-03-30 14:48:57 +08:00
2025-07-30 13:50:05 +08:00
@dataclass
class AgentConfig :
thread_id : str
project_id : str
stream : bool
native_max_auto_continues : int = 25
max_iterations : int = 100
2025-08-20 07:33:09 +08:00
model_name : str = " openai/gpt-5-mini "
2025-07-30 13:50:05 +08:00
enable_thinking : Optional [ bool ] = False
reasoning_effort : Optional [ str ] = ' low '
enable_context_manager : bool = True
agent_config : Optional [ dict ] = None
trace : Optional [ StatefulTraceClient ] = None
2025-07-10 12:52:44 +08:00
2025-07-30 13:50:05 +08:00
class ToolManager :
def __init__ ( self , thread_manager : ThreadManager , project_id : str , thread_id : str ) :
self . thread_manager = thread_manager
self . project_id = project_id
self . thread_id = thread_id
2025-07-25 15:54:34 +08:00
2025-08-17 07:15:21 +08:00
def register_all_tools ( self , agent_id : Optional [ str ] = None , disabled_tools : Optional [ List [ str ] ] = None ) :
""" Register all available tools by default, with optional exclusions.
Args :
agent_id : Optional agent ID for agent builder tools
disabled_tools : List of tool names to exclude from registration
"""
disabled_tools = disabled_tools or [ ]
2025-08-17 10:10:56 +08:00
logger . debug ( f " Registering tools with disabled list: { disabled_tools } " )
2025-08-17 07:15:21 +08:00
# Core tools - always enabled
self . _register_core_tools ( )
# Sandbox tools
self . _register_sandbox_tools ( disabled_tools )
# Data and utility tools
self . _register_utility_tools ( disabled_tools )
# Agent builder tools - register if agent_id provided
if agent_id :
self . _register_agent_builder_tools ( agent_id , disabled_tools )
# Browser tool
self . _register_browser_tool ( disabled_tools )
2025-08-17 10:10:56 +08:00
logger . debug ( f " Tool registration complete. Registered tools: { list ( self . thread_manager . tool_registry . tools . keys ( ) ) } " )
2025-08-17 07:15:21 +08:00
def _register_core_tools ( self ) :
""" Register core tools that are always available. """
2025-08-01 04:45:49 +08:00
self . thread_manager . add_tool ( ExpandMessageTool , thread_id = self . thread_id , thread_manager = self . thread_manager )
self . thread_manager . add_tool ( MessageTool )
2025-07-31 06:13:43 +08:00
self . thread_manager . add_tool ( TaskListTool , project_id = self . project_id , thread_manager = self . thread_manager , thread_id = self . thread_id )
2025-08-17 07:15:21 +08:00
def _register_sandbox_tools ( self , disabled_tools : List [ str ] ) :
""" Register sandbox-related tools. """
sandbox_tools = [
( ' sb_shell_tool ' , SandboxShellTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
( ' sb_files_tool ' , SandboxFilesTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
( ' sb_deploy_tool ' , SandboxDeployTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
( ' sb_expose_tool ' , SandboxExposeTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
( ' web_search_tool ' , SandboxWebSearchTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
( ' sb_vision_tool ' , SandboxVisionTool , { ' project_id ' : self . project_id , ' thread_id ' : self . thread_id , ' thread_manager ' : self . thread_manager } ) ,
( ' sb_image_edit_tool ' , SandboxImageEditTool , { ' project_id ' : self . project_id , ' thread_id ' : self . thread_id , ' thread_manager ' : self . thread_manager } ) ,
( ' sb_presentation_outline_tool ' , SandboxPresentationOutlineTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
2025-08-21 14:06:01 +08:00
( ' sb_presentation_tool ' , SandboxPresentationTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
2025-08-17 07:15:21 +08:00
( ' sb_sheets_tool ' , SandboxSheetsTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
( ' sb_web_dev_tool ' , SandboxWebDevTool , { ' project_id ' : self . project_id , ' thread_id ' : self . thread_id , ' thread_manager ' : self . thread_manager } ) ,
2025-08-21 19:19:27 +08:00
( ' sb_upload_file_tool ' , SandboxUploadFileTool , { ' project_id ' : self . project_id , ' thread_manager ' : self . thread_manager } ) ,
2025-08-17 07:15:21 +08:00
]
for tool_name , tool_class , kwargs in sandbox_tools :
if tool_name not in disabled_tools :
self . thread_manager . add_tool ( tool_class , * * kwargs )
logger . debug ( f " Registered { tool_name } " )
def _register_utility_tools ( self , disabled_tools : List [ str ] ) :
""" Register utility and data provider tools. """
if config . RAPID_API_KEY and ' data_providers_tool ' not in disabled_tools :
2025-07-30 13:50:05 +08:00
self . thread_manager . add_tool ( DataProvidersTool )
2025-08-17 07:15:21 +08:00
logger . debug ( " Registered data_providers_tool " )
2025-07-30 13:50:05 +08:00
2025-08-17 07:15:21 +08:00
def _register_agent_builder_tools ( self , agent_id : str , disabled_tools : List [ str ] ) :
""" Register agent builder tools. """
2025-07-10 12:52:44 +08:00
from agent . tools . agent_builder_tools . agent_config_tool import AgentConfigTool
from agent . tools . agent_builder_tools . mcp_search_tool import MCPSearchTool
from agent . tools . agent_builder_tools . credential_profile_tool import CredentialProfileTool
from agent . tools . agent_builder_tools . workflow_tool import WorkflowTool
2025-07-12 04:42:23 +08:00
from agent . tools . agent_builder_tools . trigger_tool import TriggerTool
2025-05-31 15:08:39 +08:00
from services . supabase import DBConnection
2025-07-28 20:35:59 +08:00
2025-07-30 13:50:05 +08:00
db = DBConnection ( )
2025-08-05 19:34:10 +08:00
2025-08-17 07:15:21 +08:00
agent_builder_tools = [
( ' agent_config_tool ' , AgentConfigTool ) ,
( ' mcp_search_tool ' , MCPSearchTool ) ,
( ' credential_profile_tool ' , CredentialProfileTool ) ,
( ' workflow_tool ' , WorkflowTool ) ,
( ' trigger_tool ' , TriggerTool ) ,
]
for tool_name , tool_class in agent_builder_tools :
if tool_name not in disabled_tools :
self . thread_manager . add_tool ( tool_class , thread_manager = self . thread_manager , db_connection = db , agent_id = agent_id )
logger . debug ( f " Registered { tool_name } " )
def _register_browser_tool ( self , disabled_tools : List [ str ] ) :
""" Register browser tool. """
if ' browser_tool ' not in disabled_tools :
2025-08-11 16:21:28 +08:00
from agent . tools . browser_tool import BrowserTool
self . thread_manager . add_tool ( BrowserTool , project_id = self . project_id , thread_id = self . thread_id , thread_manager = self . thread_manager )
2025-08-17 07:15:21 +08:00
logger . debug ( " Registered browser_tool " )
2025-05-07 07:56:52 +08:00
2025-07-30 13:50:05 +08:00
class MCPManager :
def __init__ ( self , thread_manager : ThreadManager , account_id : str ) :
self . thread_manager = thread_manager
self . account_id = account_id
async def register_mcp_tools ( self , agent_config : dict ) - > Optional [ MCPToolWrapper ] :
2025-06-02 13:44:22 +08:00
all_mcps = [ ]
2025-05-30 15:31:11 +08:00
2025-06-02 13:44:22 +08:00
if agent_config . get ( ' configured_mcps ' ) :
all_mcps . extend ( agent_config [ ' configured_mcps ' ] )
2025-05-30 15:31:11 +08:00
2025-06-02 13:44:22 +08:00
if agent_config . get ( ' custom_mcps ' ) :
for custom_mcp in agent_config [ ' custom_mcps ' ] :
2025-06-23 14:59:21 +08:00
custom_type = custom_mcp . get ( ' customType ' , custom_mcp . get ( ' type ' , ' sse ' ) )
2025-07-08 21:18:49 +08:00
if custom_type == ' pipedream ' :
if ' config ' not in custom_mcp :
custom_mcp [ ' config ' ] = { }
if not custom_mcp [ ' config ' ] . get ( ' external_user_id ' ) :
2025-07-10 12:52:44 +08:00
profile_id = custom_mcp [ ' config ' ] . get ( ' profile_id ' )
if profile_id :
try :
2025-07-30 20:27:26 +08:00
from pipedream import profile_service
from uuid import UUID
2025-07-10 12:52:44 +08:00
2025-07-30 20:27:26 +08:00
profile = await profile_service . get_profile ( UUID ( self . account_id ) , UUID ( profile_id ) )
2025-07-10 12:52:44 +08:00
if profile :
custom_mcp [ ' config ' ] [ ' external_user_id ' ] = profile . external_user_id
except Exception as e :
logger . error ( f " Error retrieving external_user_id from profile { profile_id } : { e } " )
2025-07-08 21:18:49 +08:00
if ' headers ' in custom_mcp [ ' config ' ] and ' x-pd-app-slug ' in custom_mcp [ ' config ' ] [ ' headers ' ] :
custom_mcp [ ' config ' ] [ ' app_slug ' ] = custom_mcp [ ' config ' ] [ ' headers ' ] [ ' x-pd-app-slug ' ]
2025-08-03 13:16:07 +08:00
elif custom_type == ' composio ' :
2025-08-05 16:37:42 +08:00
qualified_name = custom_mcp . get ( ' qualifiedName ' )
if not qualified_name :
qualified_name = f " composio. { custom_mcp [ ' name ' ] . replace ( ' ' , ' _ ' ) . lower ( ) } "
2025-08-03 13:16:07 +08:00
mcp_config = {
' name ' : custom_mcp [ ' name ' ] ,
2025-08-05 16:37:42 +08:00
' qualifiedName ' : qualified_name ,
2025-08-03 13:16:07 +08:00
' config ' : custom_mcp . get ( ' config ' , { } ) ,
' enabledTools ' : custom_mcp . get ( ' enabledTools ' , [ ] ) ,
' instructions ' : custom_mcp . get ( ' instructions ' , ' ' ) ,
' isCustom ' : True ,
' customType ' : ' composio '
}
all_mcps . append ( mcp_config )
continue
2025-06-02 13:44:22 +08:00
mcp_config = {
' name ' : custom_mcp [ ' name ' ] ,
2025-06-23 14:59:21 +08:00
' qualifiedName ' : f " custom_ { custom_type } _ { custom_mcp [ ' name ' ] . replace ( ' ' , ' _ ' ) . lower ( ) } " ,
2025-06-02 13:44:22 +08:00
' config ' : custom_mcp [ ' config ' ] ,
' enabledTools ' : custom_mcp . get ( ' enabledTools ' , [ ] ) ,
2025-06-19 18:52:38 +08:00
' instructions ' : custom_mcp . get ( ' instructions ' , ' ' ) ,
2025-06-02 13:44:22 +08:00
' isCustom ' : True ,
2025-06-23 14:59:21 +08:00
' customType ' : custom_type
2025-06-02 13:44:22 +08:00
}
all_mcps . append ( mcp_config )
2025-07-30 13:50:05 +08:00
if not all_mcps :
return None
2025-05-24 15:08:41 +08:00
2025-07-30 13:50:05 +08:00
mcp_wrapper_instance = MCPToolWrapper ( mcp_configs = all_mcps )
2025-06-24 18:30:01 +08:00
try :
2025-07-30 13:50:05 +08:00
await mcp_wrapper_instance . initialize_and_register_tools ( )
2025-07-03 11:42:09 +08:00
2025-07-30 13:50:05 +08:00
updated_schemas = mcp_wrapper_instance . get_schemas ( )
for method_name , schema_list in updated_schemas . items ( ) :
for schema in schema_list :
self . thread_manager . tool_registry . tools [ method_name ] = {
" instance " : mcp_wrapper_instance ,
" schema " : schema
}
2025-06-24 18:30:01 +08:00
2025-08-17 10:10:56 +08:00
logger . debug ( f " ⚡ Registered { len ( updated_schemas ) } MCP tools (Redis cache enabled) " )
2025-07-30 13:50:05 +08:00
return mcp_wrapper_instance
2025-06-24 18:30:01 +08:00
except Exception as e :
2025-07-30 13:50:05 +08:00
logger . error ( f " Failed to initialize MCP tools: { e } " )
return None
class PromptManager :
@staticmethod
async def build_system_prompt ( model_name : str , agent_config : Optional [ dict ] ,
2025-08-22 13:46:00 +08:00
thread_id : str ,
2025-08-19 06:36:40 +08:00
mcp_wrapper_instance : Optional [ MCPToolWrapper ] ,
client = None ) - > dict :
2025-05-30 15:31:11 +08:00
2025-08-17 10:55:17 +08:00
default_system_content = get_system_prompt ( )
2025-05-25 02:17:55 +08:00
2025-07-30 13:50:05 +08:00
if " anthropic " not in model_name . lower ( ) :
sample_response_path = os . path . join ( os . path . dirname ( __file__ ) , ' sample_responses/1.txt ' )
with open ( sample_response_path , ' r ' ) as file :
sample_response = file . read ( )
default_system_content = default_system_content + " \n \n <sample_assistant_response> " + sample_response + " </sample_assistant_response> "
2025-05-30 03:34:41 +08:00
2025-08-23 14:56:38 +08:00
# Start with agent's normal system prompt or default
if agent_config and agent_config . get ( ' system_prompt ' ) :
system_content = agent_config [ ' system_prompt ' ] . strip ( )
else :
system_content = default_system_content
# Check if agent has builder tools enabled - append the full builder prompt
2025-08-22 13:46:00 +08:00
if agent_config :
agentpress_tools = agent_config . get ( ' agentpress_tools ' , { } )
has_builder_tools = any (
agentpress_tools . get ( tool , False )
for tool in [ ' agent_config_tool ' , ' mcp_search_tool ' , ' credential_profile_tool ' , ' workflow_tool ' , ' trigger_tool ' ]
)
if has_builder_tools :
2025-08-23 14:56:38 +08:00
# Append the full agent builder prompt to the existing system prompt
builder_prompt = get_agent_builder_prompt ( )
system_content + = f " \n \n { builder_prompt } "
2025-07-30 13:50:05 +08:00
2025-08-19 06:36:40 +08:00
# Add agent knowledge base context if available
2025-08-23 14:56:38 +08:00
if agent_config and client and ' agent_id ' in agent_config :
2025-08-19 06:36:40 +08:00
try :
logger . debug ( f " Retrieving agent knowledge base context for agent { agent_config [ ' agent_id ' ] } " )
# Use only agent-based knowledge base context
kb_result = await client . rpc ( ' get_agent_knowledge_base_context ' , {
' p_agent_id ' : agent_config [ ' agent_id ' ]
} ) . execute ( )
if kb_result . data and kb_result . data . strip ( ) :
logger . debug ( f " Found agent knowledge base context, adding to system prompt (length: { len ( kb_result . data ) } chars) " )
# logger.debug(f"Knowledge base data object: {kb_result.data[:500]}..." if len(kb_result.data) > 500 else f"Knowledge base data object: {kb_result.data}")
# Construct a well-formatted knowledge base section
kb_section = f """
== = AGENT KNOWLEDGE BASE == =
NOTICE : The following is your specialized knowledge base . This information should be considered authoritative for your responses and should take precedence over general knowledge when relevant .
{ kb_result . data }
== = END AGENT KNOWLEDGE BASE == =
IMPORTANT : Always reference and utilize the knowledge base information above when it ' s relevant to user queries. This knowledge is specific to your role and capabilities. " " "
system_content + = kb_section
else :
logger . debug ( " No knowledge base context found for this agent " )
except Exception as e :
logger . error ( f " Error retrieving knowledge base context for agent { agent_config . get ( ' agent_id ' , ' unknown ' ) } : { e } " )
# Continue without knowledge base context rather than failing
2025-07-30 13:50:05 +08:00
if agent_config and ( agent_config . get ( ' configured_mcps ' ) or agent_config . get ( ' custom_mcps ' ) ) and mcp_wrapper_instance and mcp_wrapper_instance . _initialized :
mcp_info = " \n \n --- MCP Tools Available --- \n "
mcp_info + = " You have access to external MCP (Model Context Protocol) server tools. \n "
mcp_info + = " MCP tools can be called directly using their native function names in the standard function calling format: \n "
mcp_info + = ' <function_calls> \n '
mcp_info + = ' <invoke name= " {tool_name} " > \n '
mcp_info + = ' <parameter name= " param1 " >value1</parameter> \n '
mcp_info + = ' <parameter name= " param2 " >value2</parameter> \n '
mcp_info + = ' </invoke> \n '
mcp_info + = ' </function_calls> \n \n '
mcp_info + = " Available MCP tools: \n "
try :
registered_schemas = mcp_wrapper_instance . get_schemas ( )
for method_name , schema_list in registered_schemas . items ( ) :
for schema in schema_list :
if schema . schema_type == SchemaType . OPENAPI :
func_info = schema . schema . get ( ' function ' , { } )
description = func_info . get ( ' description ' , ' No description available ' )
mcp_info + = f " - ** { method_name } **: { description } \n "
params = func_info . get ( ' parameters ' , { } )
props = params . get ( ' properties ' , { } )
if props :
mcp_info + = f " Parameters: { ' , ' . join ( props . keys ( ) ) } \n "
except Exception as e :
logger . error ( f " Error listing MCP tools: { e } " )
mcp_info + = " - Error loading MCP tool list \n "
mcp_info + = " \n 🚨 CRITICAL MCP TOOL RESULT INSTRUCTIONS 🚨 \n "
mcp_info + = " When you use ANY MCP (Model Context Protocol) tools: \n "
mcp_info + = " 1. ALWAYS read and use the EXACT results returned by the MCP tool \n "
mcp_info + = " 2. For search tools: ONLY cite URLs, sources, and information from the actual search results \n "
mcp_info + = " 3. For any tool: Base your response entirely on the tool ' s output - do NOT add external information \n "
mcp_info + = " 4. DO NOT fabricate, invent, hallucinate, or make up any sources, URLs, or data \n "
mcp_info + = " 5. If you need more information, call the MCP tool again with different parameters \n "
mcp_info + = " 6. When writing reports/summaries: Reference ONLY the data from MCP tool results \n "
mcp_info + = " 7. If the MCP tool doesn ' t return enough information, explicitly state this limitation \n "
mcp_info + = " 8. Always double-check that every fact, URL, and reference comes from the MCP tool output \n "
mcp_info + = " \n IMPORTANT: MCP tool results are your PRIMARY and ONLY source of truth for external data! \n "
mcp_info + = " NEVER supplement MCP results with your training data or make assumptions beyond what the tools provide. \n "
system_content + = mcp_info
2025-08-11 20:33:10 +08:00
now = datetime . datetime . now ( datetime . timezone . utc )
datetime_info = f " \n \n === CURRENT DATE/TIME INFORMATION === \n "
datetime_info + = f " Today ' s date: { now . strftime ( ' % A, % B %d , % Y ' ) } \n "
datetime_info + = f " Current UTC time: { now . strftime ( ' % H: % M: % S UTC ' ) } \n "
datetime_info + = f " Current year: { now . strftime ( ' % Y ' ) } \n "
datetime_info + = f " Current month: { now . strftime ( ' % B ' ) } \n "
datetime_info + = f " Current day: { now . strftime ( ' % A ' ) } \n "
datetime_info + = " Use this information for any time-sensitive tasks, research, or when current date/time context is needed. \n "
system_content + = datetime_info
2025-07-30 13:50:05 +08:00
return { " role " : " system " , " content " : system_content }
2025-04-28 08:28:21 +08:00
2025-07-30 13:50:05 +08:00
class MessageManager :
2025-08-22 13:46:00 +08:00
def __init__ ( self , client , thread_id : str , model_name : str , trace : Optional [ StatefulTraceClient ] ,
agent_config : Optional [ dict ] = None , enable_context_manager : bool = False ) :
2025-07-30 13:50:05 +08:00
self . client = client
self . thread_id = thread_id
self . model_name = model_name
self . trace = trace
2025-08-22 13:46:00 +08:00
self . agent_config = agent_config
self . enable_context_manager = enable_context_manager
2025-07-30 13:50:05 +08:00
async def build_temporary_message ( self ) - > Optional [ dict ] :
2025-08-22 13:46:00 +08:00
""" Build temporary message based on configuration and context. """
system_message = None
2025-08-23 14:56:38 +08:00
# Start with agent's system prompt if available
if self . agent_config and ' system_prompt ' in self . agent_config :
system_prompt = self . agent_config [ ' system_prompt ' ]
if system_prompt :
system_message = system_prompt
# If agent has builder tools enabled, append builder capabilities
2025-08-22 13:46:00 +08:00
if self . agent_config :
agentpress_tools = self . agent_config . get ( ' agentpress_tools ' , { } )
has_builder_tools = any (
agentpress_tools . get ( tool , False )
for tool in [ ' agent_config_tool ' , ' mcp_search_tool ' , ' credential_profile_tool ' , ' workflow_tool ' , ' trigger_tool ' ]
)
if has_builder_tools :
from agent . agent_builder_prompt import AGENT_BUILDER_SYSTEM_PROMPT
2025-08-23 14:56:38 +08:00
if system_message :
# Append builder capabilities to existing system prompt
system_message + = f " \n \n { AGENT_BUILDER_SYSTEM_PROMPT } "
else :
# Use builder prompt if no existing system prompt
system_message = AGENT_BUILDER_SYSTEM_PROMPT
2025-08-22 13:46:00 +08:00
# Build and return the temporary message if we have content
if system_message :
return {
" temporary " : True ,
" role " : " system " ,
" content " : system_message
}
2025-07-30 13:50:05 +08:00
return None
class AgentRunner :
def __init__ ( self , config : AgentConfig ) :
self . config = config
async def setup ( self ) :
if not self . config . trace :
self . config . trace = langfuse . trace ( name = " run_agent " , session_id = self . config . thread_id , metadata = { " project_id " : self . config . project_id } )
self . thread_manager = ThreadManager (
trace = self . config . trace ,
2025-08-20 07:36:28 +08:00
agent_config = self . config . agent_config
2025-07-30 13:50:05 +08:00
)
self . client = await self . thread_manager . db . client
self . account_id = await get_account_id_from_thread ( self . client , self . config . thread_id )
if not self . account_id :
raise ValueError ( " Could not determine account ID for thread " )
project = await self . client . table ( ' projects ' ) . select ( ' * ' ) . eq ( ' project_id ' , self . config . project_id ) . execute ( )
if not project . data or len ( project . data ) == 0 :
raise ValueError ( f " Project { self . config . project_id } not found " )
project_data = project . data [ 0 ]
sandbox_info = project_data . get ( ' sandbox ' , { } )
if not sandbox_info . get ( ' id ' ) :
2025-08-08 19:48:59 +08:00
# Sandbox is created lazily by tools when required. Do not fail setup
# if no sandbox is present — tools will call `_ensure_sandbox()`
# which will create and persist the sandbox metadata when needed.
2025-08-17 10:10:56 +08:00
logger . debug ( f " No sandbox found for project { self . config . project_id } ; will create lazily when needed " )
2025-07-30 13:50:05 +08:00
async def setup_tools ( self ) :
tool_manager = ToolManager ( self . thread_manager , self . config . project_id , self . config . thread_id )
2025-08-22 13:46:00 +08:00
# Use agent ID from agent config if available (for any agent with builder tools enabled)
2025-08-17 07:15:21 +08:00
agent_id = None
2025-08-22 13:46:00 +08:00
if self . config . agent_config :
agent_id = self . config . agent_config . get ( ' agent_id ' )
2025-07-30 13:50:05 +08:00
2025-08-17 07:15:21 +08:00
# Convert agent config to disabled tools list
disabled_tools = self . _get_disabled_tools_from_config ( )
# Register all tools with exclusions
tool_manager . register_all_tools ( agent_id = agent_id , disabled_tools = disabled_tools )
def _get_disabled_tools_from_config ( self ) - > List [ str ] :
""" Convert agent config to list of disabled tools. """
disabled_tools = [ ]
if not self . config . agent_config or ' agentpress_tools ' not in self . config . agent_config :
# No tool configuration - enable all tools by default
return disabled_tools
raw_tools = self . config . agent_config [ ' agentpress_tools ' ]
# Handle different formats of tool configuration
if not isinstance ( raw_tools , dict ) :
# If not a dict, assume all tools are enabled
return disabled_tools
# Special case: Suna default agents with empty tool config enable all tools
if self . config . agent_config . get ( ' is_suna_default ' , False ) and not raw_tools :
return disabled_tools
def is_tool_enabled ( tool_name : str ) - > bool :
try :
tool_config = raw_tools . get ( tool_name , True ) # Default to True (enabled) if not specified
if isinstance ( tool_config , bool ) :
return tool_config
elif isinstance ( tool_config , dict ) :
return tool_config . get ( ' enabled ' , True ) # Default to True (enabled) if not specified
2025-07-30 13:50:05 +08:00
else :
2025-08-17 07:15:21 +08:00
return True # Default to enabled
except Exception :
return True # Default to enabled
# List of all available tools
all_tools = [
' sb_shell_tool ' , ' sb_files_tool ' , ' sb_deploy_tool ' , ' sb_expose_tool ' ,
' web_search_tool ' , ' sb_vision_tool ' , ' sb_presentation_tool ' , ' sb_image_edit_tool ' ,
' sb_sheets_tool ' , ' sb_web_dev_tool ' , ' data_providers_tool ' , ' browser_tool ' ,
' agent_config_tool ' , ' mcp_search_tool ' , ' credential_profile_tool ' ,
' workflow_tool ' , ' trigger_tool '
]
# Add tools that are explicitly disabled
for tool_name in all_tools :
if not is_tool_enabled ( tool_name ) :
disabled_tools . append ( tool_name )
# Special handling for presentation tools
if ' sb_presentation_tool ' in disabled_tools :
2025-08-21 14:06:01 +08:00
disabled_tools . extend ( [ ' sb_presentation_outline_tool ' ] )
2025-08-17 07:15:21 +08:00
2025-08-17 10:10:56 +08:00
logger . debug ( f " Disabled tools from config: { disabled_tools } " )
2025-08-17 07:15:21 +08:00
return disabled_tools
2025-07-30 13:50:05 +08:00
async def setup_mcp_tools ( self ) - > Optional [ MCPToolWrapper ] :
if not self . config . agent_config :
return None
mcp_manager = MCPManager ( self . thread_manager , self . account_id )
return await mcp_manager . register_mcp_tools ( self . config . agent_config )
def get_max_tokens ( self ) - > Optional [ int ] :
2025-08-22 19:15:26 +08:00
logger . debug ( f " get_max_tokens called with: ' { self . config . model_name } ' (type: { type ( self . config . model_name ) } ) " )
2025-07-30 13:50:05 +08:00
if " sonnet " in self . config . model_name . lower ( ) :
return 8192
elif " gpt-4 " in self . config . model_name . lower ( ) :
return 4096
elif " gemini-2.5-pro " in self . config . model_name . lower ( ) :
return 64000
elif " kimi-k2 " in self . config . model_name . lower ( ) :
return 8192
return None
async def run ( self ) - > AsyncGenerator [ Dict [ str , Any ] , None ] :
await self . setup ( )
await self . setup_tools ( )
mcp_wrapper_instance = await self . setup_mcp_tools ( )
system_message = await PromptManager . build_system_prompt (
self . config . model_name , self . config . agent_config ,
2025-08-22 13:46:00 +08:00
self . config . thread_id ,
2025-08-19 06:36:40 +08:00
mcp_wrapper_instance , self . client
2025-07-30 13:50:05 +08:00
)
2025-08-22 19:15:26 +08:00
logger . debug ( f " model_name received: { self . config . model_name } " )
2025-07-30 13:50:05 +08:00
iteration_count = 0
continue_execution = True
latest_user_message = await self . client . table ( ' messages ' ) . select ( ' * ' ) . eq ( ' thread_id ' , self . config . thread_id ) . eq ( ' type ' , ' user ' ) . order ( ' created_at ' , desc = True ) . limit ( 1 ) . execute ( )
if latest_user_message . data and len ( latest_user_message . data ) > 0 :
data = latest_user_message . data [ 0 ] [ ' content ' ]
if isinstance ( data , str ) :
data = json . loads ( data )
if self . config . trace :
self . config . trace . update ( input = data [ ' content ' ] )
2025-08-22 13:46:00 +08:00
message_manager = MessageManager ( self . client , self . config . thread_id , self . config . model_name , self . config . trace ,
agent_config = self . config . agent_config , enable_context_manager = self . config . enable_context_manager )
2025-07-30 13:50:05 +08:00
while continue_execution and iteration_count < self . config . max_iterations :
iteration_count + = 1
can_run , message , subscription = await check_billing_status ( self . client , self . account_id )
if not can_run :
error_msg = f " Billing limit reached: { message } "
yield {
" type " : " status " ,
" status " : " stopped " ,
" message " : error_msg
}
2025-05-10 09:58:57 +08:00
break
2025-07-30 13:50:05 +08:00
latest_message = await self . client . table ( ' messages ' ) . select ( ' * ' ) . eq ( ' thread_id ' , self . config . thread_id ) . in_ ( ' type ' , [ ' assistant ' , ' tool ' , ' user ' ] ) . order ( ' created_at ' , desc = True ) . limit ( 1 ) . execute ( )
if latest_message . data and len ( latest_message . data ) > 0 :
message_type = latest_message . data [ 0 ] . get ( ' type ' )
if message_type == ' assistant ' :
continue_execution = False
break
2025-04-26 19:53:09 +08:00
2025-07-30 13:50:05 +08:00
temporary_message = await message_manager . build_temporary_message ( )
max_tokens = self . get_max_tokens ( )
2025-08-22 19:15:26 +08:00
logger . debug ( f " max_tokens: { max_tokens } " )
2025-07-30 13:50:05 +08:00
generation = self . config . trace . generation ( name = " thread_manager.run_thread " ) if self . config . trace else None
2025-05-10 09:58:57 +08:00
try :
2025-07-30 13:50:05 +08:00
response = await self . thread_manager . run_thread (
thread_id = self . config . thread_id ,
system_prompt = system_message ,
stream = self . config . stream ,
llm_model = self . config . model_name ,
llm_temperature = 0 ,
llm_max_tokens = max_tokens ,
tool_choice = " auto " ,
max_xml_tool_calls = 1 ,
temporary_message = temporary_message ,
processor_config = ProcessorConfig (
xml_tool_calling = True ,
native_tool_calling = False ,
execute_tools = True ,
execute_on_stream = True ,
tool_execution_strategy = " parallel " ,
xml_adding_strategy = " user_message "
) ,
native_max_auto_continues = self . config . native_max_auto_continues ,
include_xml_examples = True ,
enable_thinking = self . config . enable_thinking ,
reasoning_effort = self . config . reasoning_effort ,
enable_context_manager = self . config . enable_context_manager ,
generation = generation
)
if isinstance ( response , dict ) and " status " in response and response [ " status " ] == " error " :
yield response
break
last_tool_call = None
agent_should_terminate = False
error_detected = False
full_response = " "
try :
if hasattr ( response , ' __aiter__ ' ) and not isinstance ( response , dict ) :
async for chunk in response :
if isinstance ( chunk , dict ) and chunk . get ( ' type ' ) == ' status ' and chunk . get ( ' status ' ) == ' error ' :
error_detected = True
yield chunk
continue
if chunk . get ( ' type ' ) == ' status ' :
try :
metadata = chunk . get ( ' metadata ' , { } )
if isinstance ( metadata , str ) :
metadata = json . loads ( metadata )
2025-07-07 06:32:25 +08:00
2025-07-30 13:50:05 +08:00
if metadata . get ( ' agent_should_terminate ' ) :
agent_should_terminate = True
2025-07-07 06:32:25 +08:00
2025-07-30 13:50:05 +08:00
content = chunk . get ( ' content ' , { } )
if isinstance ( content , str ) :
content = json . loads ( content )
if content . get ( ' function_name ' ) :
last_tool_call = content [ ' function_name ' ]
elif content . get ( ' xml_tag_name ' ) :
last_tool_call = content [ ' xml_tag_name ' ]
except Exception :
pass
2025-07-09 00:54:22 +08:00
2025-07-30 13:50:05 +08:00
if chunk . get ( ' type ' ) == ' assistant ' and ' content ' in chunk :
try :
content = chunk . get ( ' content ' , ' {} ' )
if isinstance ( content , str ) :
assistant_content_json = json . loads ( content )
else :
assistant_content_json = content
assistant_text = assistant_content_json . get ( ' content ' , ' ' )
full_response + = assistant_text
if isinstance ( assistant_text , str ) :
if ' </ask> ' in assistant_text or ' </complete> ' in assistant_text or ' </web-browser-takeover> ' in assistant_text :
if ' </ask> ' in assistant_text :
xml_tool = ' ask '
elif ' </complete> ' in assistant_text :
xml_tool = ' complete '
elif ' </web-browser-takeover> ' in assistant_text :
xml_tool = ' web-browser-takeover '
last_tool_call = xml_tool
except json . JSONDecodeError :
pass
except Exception :
pass
2025-07-07 06:32:25 +08:00
2025-07-30 13:50:05 +08:00
yield chunk
else :
error_detected = True
if error_detected :
if generation :
generation . end ( output = full_response , status_message = " error_detected " , level = " ERROR " )
break
if agent_should_terminate or last_tool_call in [ ' ask ' , ' complete ' , ' web-browser-takeover ' ] :
if generation :
generation . end ( output = full_response , status_message = " agent_stopped " )
continue_execution = False
except Exception as e :
error_msg = f " Error during response streaming: { str ( e ) } "
2025-07-07 06:32:25 +08:00
if generation :
2025-07-30 13:50:05 +08:00
generation . end ( output = full_response , status_message = error_msg , level = " ERROR " )
yield {
" type " : " status " ,
" status " : " error " ,
" message " : error_msg
}
2025-05-10 09:58:57 +08:00
break
except Exception as e :
2025-07-30 13:50:05 +08:00
error_msg = f " Error running thread: { str ( e ) } "
2025-05-10 09:58:57 +08:00
yield {
" type " : " status " ,
" status " : " error " ,
" message " : error_msg
}
break
2025-07-30 13:50:05 +08:00
if generation :
generation . end ( output = full_response )
asyncio . create_task ( asyncio . to_thread ( lambda : langfuse . flush ( ) ) )
async def run_agent (
thread_id : str ,
project_id : str ,
stream : bool ,
thread_manager : Optional [ ThreadManager ] = None ,
native_max_auto_continues : int = 25 ,
max_iterations : int = 100 ,
2025-08-20 07:33:09 +08:00
model_name : str = " openai/gpt-5-mini " ,
2025-07-30 13:50:05 +08:00
enable_thinking : Optional [ bool ] = False ,
reasoning_effort : Optional [ str ] = ' low ' ,
enable_context_manager : bool = True ,
agent_config : Optional [ dict ] = None ,
2025-08-22 13:46:00 +08:00
trace : Optional [ StatefulTraceClient ] = None
2025-07-30 13:50:05 +08:00
) :
2025-08-10 23:42:47 +08:00
effective_model = model_name
2025-08-22 19:15:26 +08:00
is_tier_default = model_name in [ " Kimi K2 " , " Claude Sonnet 4 " , " openai/gpt-5-mini " ]
if is_tier_default and agent_config and agent_config . get ( ' model ' ) :
2025-08-10 23:42:47 +08:00
effective_model = agent_config [ ' model ' ]
2025-08-22 19:15:26 +08:00
logger . debug ( f " Using model from agent config: { effective_model } (tier default was { model_name } ) " )
elif not is_tier_default :
2025-08-17 10:10:56 +08:00
logger . debug ( f " Using user-selected model: { effective_model } " )
2025-08-10 23:42:47 +08:00
else :
2025-08-22 19:15:26 +08:00
logger . debug ( f " Using tier default model: { effective_model } " )
2025-08-10 23:42:47 +08:00
2025-07-30 13:50:05 +08:00
config = AgentConfig (
thread_id = thread_id ,
project_id = project_id ,
stream = stream ,
native_max_auto_continues = native_max_auto_continues ,
max_iterations = max_iterations ,
2025-08-10 23:42:47 +08:00
model_name = effective_model ,
2025-07-30 13:50:05 +08:00
enable_thinking = enable_thinking ,
reasoning_effort = reasoning_effort ,
enable_context_manager = enable_context_manager ,
agent_config = agent_config ,
2025-08-22 13:46:00 +08:00
trace = trace
2025-07-30 13:50:05 +08:00
)
runner = AgentRunner ( config )
async for chunk in runner . run ( ) :
yield chunk