mirror of https://github.com/kortix-ai/suna.git
341 lines
17 KiB
Python
341 lines
17 KiB
Python
import json
|
|
from typing import Optional, Dict, Any
|
|
from agentpress.tool import ToolResult, openapi_schema, usage_example
|
|
from agentpress.thread_manager import ThreadManager
|
|
from .base_tool import AgentBuilderBaseTool
|
|
from utils.logger import logger
|
|
|
|
|
|
|
|
class AgentConfigTool(AgentBuilderBaseTool):
|
|
def __init__(self, thread_manager: ThreadManager, db_connection, agent_id: str):
|
|
super().__init__(thread_manager, db_connection, agent_id)
|
|
|
|
@openapi_schema({
|
|
"type": "function",
|
|
"function": {
|
|
"name": "update_agent",
|
|
"description": "Update the agent's configuration including name, description, tools, and MCP servers. System prompt additions are appended to existing instructions with high priority markers. Call this whenever the user wants to modify any aspect of the agent.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "The name of the agent. Should be descriptive and indicate the agent's purpose."
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "A brief description of what the agent does and its capabilities."
|
|
},
|
|
"system_prompt": {
|
|
"type": "string",
|
|
"description": "Critical additional instructions that define agent's behavior, expertise and approach to append with HIGH PRIORITY. Must be concise and specific. Use imperative verbs, avoid filler words. Include 'Act as [role]' statement where role is the agent's primary function (e.g., 'Act as a lawyer', 'Act as security officer', 'Act as medical assistant')."
|
|
},
|
|
"agentpress_tools": {
|
|
"type": "object",
|
|
"description": "Configuration for AgentPress tools. Each key is a tool name, and the value is a boolean indicating if the tool is enabled.",
|
|
"additionalProperties": {
|
|
"type": "boolean"
|
|
}
|
|
},
|
|
"configured_mcps": {
|
|
"type": "array",
|
|
"description": "List of configured MCP servers for external integrations.",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"qualifiedName": {"type": "string"},
|
|
"config": {"type": "object"},
|
|
"enabledTools": {
|
|
"type": "array",
|
|
"items": {"type": "string"}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
"avatar": {
|
|
"type": "string",
|
|
"description": "Emoji to use as the agent's avatar."
|
|
},
|
|
"avatar_color": {
|
|
"type": "string",
|
|
"description": "Hex color code for the agent's avatar background."
|
|
}
|
|
},
|
|
"required": []
|
|
}
|
|
}
|
|
})
|
|
@usage_example('''
|
|
<function_calls>
|
|
<invoke name="update_agent">
|
|
<parameter name="name">Research Assistant</parameter>
|
|
<parameter name="description">An AI assistant specialized in conducting research and providing comprehensive analysis</parameter>
|
|
<parameter name="system_prompt">Act as a research analyst. Always verify sources</parameter>
|
|
<parameter name="agentpress_tools">{"web_search_tool": true, "sb_files_tool": true, "sb_shell_tool": false}</parameter>
|
|
<parameter name="avatar">🔬</parameter>
|
|
<parameter name="avatar_color">#4F46E5</parameter>
|
|
</invoke>
|
|
</function_calls>
|
|
''')
|
|
async def update_agent(
|
|
self,
|
|
name: Optional[str] = None,
|
|
description: Optional[str] = None,
|
|
system_prompt: Optional[str] = None,
|
|
agentpress_tools: Optional[Dict[str, Dict[str, Any]]] = None,
|
|
configured_mcps: Optional[list] = None,
|
|
avatar: Optional[str] = None,
|
|
avatar_color: Optional[str] = None
|
|
) -> ToolResult:
|
|
try:
|
|
account_id = await self._get_current_account_id()
|
|
client = await self.db.client
|
|
|
|
agent_result = await client.table('agents').select('*').eq('agent_id', self.agent_id).execute()
|
|
if not agent_result.data:
|
|
return self.fail_response("Agent not found")
|
|
|
|
current_agent = agent_result.data[0]
|
|
|
|
metadata = current_agent.get('metadata', {})
|
|
is_suna_default = metadata.get('is_suna_default', False)
|
|
|
|
# Enforce Suna restrictions (simplified)
|
|
if is_suna_default:
|
|
restricted_fields = []
|
|
if name is not None:
|
|
restricted_fields.append("name")
|
|
if system_prompt is not None:
|
|
restricted_fields.append("system prompt")
|
|
if agentpress_tools is not None:
|
|
restricted_fields.append("core tools")
|
|
|
|
if restricted_fields:
|
|
return self.fail_response(
|
|
f"Cannot modify {', '.join(restricted_fields)} for Suna. "
|
|
f"Suna's core identity is centrally managed. You can still add MCPs, workflows, and triggers."
|
|
)
|
|
|
|
agent_update_fields = {}
|
|
if name is not None:
|
|
agent_update_fields["name"] = name
|
|
if description is not None:
|
|
agent_update_fields["description"] = description
|
|
if avatar is not None:
|
|
agent_update_fields["avatar"] = avatar
|
|
if avatar_color is not None:
|
|
agent_update_fields["avatar_color"] = avatar_color
|
|
|
|
config_changed = (system_prompt is not None or agentpress_tools is not None or configured_mcps is not None)
|
|
|
|
if not agent_update_fields and not config_changed:
|
|
return self.fail_response("No fields provided to update")
|
|
|
|
if agent_update_fields:
|
|
result = await client.table('agents').update(agent_update_fields).eq('agent_id', self.agent_id).execute()
|
|
if not result.data:
|
|
return self.fail_response("Failed to update agent")
|
|
|
|
version_created = False
|
|
if config_changed:
|
|
try:
|
|
from agent.versioning.version_service import get_version_service
|
|
current_version = None
|
|
if current_agent.get('current_version_id'):
|
|
try:
|
|
version_service = await get_version_service()
|
|
current_version_obj = await version_service.get_version(
|
|
agent_id=self.agent_id,
|
|
version_id=current_agent['current_version_id'],
|
|
user_id=account_id
|
|
)
|
|
current_version = current_version_obj.to_dict()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to get current version: {e}")
|
|
|
|
if not current_version:
|
|
return self.fail_response("No current version found to update from")
|
|
|
|
if system_prompt is not None:
|
|
existing_prompt = current_version.get('system_prompt', '')
|
|
priority_addition = f"\n\n=== CRITICAL ADDITION ===\n{system_prompt}\n=== END CRITICAL ADDITION ==="
|
|
current_system_prompt = f"{existing_prompt}{priority_addition}" if existing_prompt else system_prompt
|
|
else:
|
|
current_system_prompt = current_version.get('system_prompt', '')
|
|
|
|
if agentpress_tools is not None:
|
|
formatted_tools = {}
|
|
for tool_name, tool_config in agentpress_tools.items():
|
|
if isinstance(tool_config, dict):
|
|
if tool_config == {}:
|
|
formatted_tools[tool_name] = True
|
|
else:
|
|
formatted_tools[tool_name] = tool_config.get("enabled", False)
|
|
else:
|
|
formatted_tools[tool_name] = bool(tool_config)
|
|
current_agentpress_tools = formatted_tools
|
|
else:
|
|
current_agentpress_tools = current_version.get('agentpress_tools', {})
|
|
|
|
current_configured_mcps = current_version.get('configured_mcps', [])
|
|
if configured_mcps is not None:
|
|
if isinstance(configured_mcps, str):
|
|
configured_mcps = json.loads(configured_mcps)
|
|
|
|
def get_mcp_identifier(mcp):
|
|
if not isinstance(mcp, dict):
|
|
return None
|
|
return (
|
|
mcp.get('qualifiedName') or
|
|
mcp.get('name') or
|
|
f"{mcp.get('type', 'unknown')}_{mcp.get('config', {}).get('url', 'nourl')}" or
|
|
str(hash(json.dumps(mcp, sort_keys=True)))
|
|
)
|
|
|
|
merged_mcps = []
|
|
existing_identifiers = set()
|
|
|
|
for existing_mcp in current_configured_mcps:
|
|
identifier = get_mcp_identifier(existing_mcp)
|
|
if identifier:
|
|
existing_identifiers.add(identifier)
|
|
merged_mcps.append(existing_mcp)
|
|
|
|
for new_mcp in configured_mcps:
|
|
identifier = get_mcp_identifier(new_mcp)
|
|
|
|
if identifier and identifier in existing_identifiers:
|
|
for i, existing_mcp in enumerate(merged_mcps):
|
|
if get_mcp_identifier(existing_mcp) == identifier:
|
|
merged_mcps[i] = new_mcp
|
|
break
|
|
else:
|
|
merged_mcps.append(new_mcp)
|
|
if identifier:
|
|
existing_identifiers.add(identifier)
|
|
|
|
current_configured_mcps = merged_mcps
|
|
logger.debug(f"MCP merge result: {len(current_configured_mcps)} total MCPs (was {len(current_version.get('configured_mcps', []))}, adding {len(configured_mcps)})")
|
|
|
|
current_custom_mcps = current_version.get('custom_mcps', [])
|
|
|
|
version_service = await get_version_service()
|
|
|
|
|
|
new_version = await version_service.create_version(
|
|
agent_id=self.agent_id,
|
|
user_id=account_id,
|
|
system_prompt=current_system_prompt,
|
|
configured_mcps=current_configured_mcps,
|
|
custom_mcps=current_custom_mcps,
|
|
agentpress_tools=current_agentpress_tools,
|
|
version_name=f"v{current_version.get('version_number', 1) + 1}",
|
|
change_description="Updated via agent builder"
|
|
)
|
|
|
|
version_created = True
|
|
logger.debug(f"Created new version {new_version.version_id} for agent {self.agent_id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to create new version: {e}")
|
|
return self.fail_response(f"Failed to create new version: {str(e)}")
|
|
|
|
agent_result = await client.table('agents').select('*').eq('agent_id', self.agent_id).execute()
|
|
updated_agent = agent_result.data[0] if agent_result.data else current_agent
|
|
|
|
updated_fields = list(agent_update_fields.keys())
|
|
if version_created:
|
|
updated_fields.append("version_created")
|
|
|
|
return self.success_response({
|
|
"message": "Agent updated successfully",
|
|
"updated_fields": updated_fields,
|
|
"agent": updated_agent,
|
|
"version_created": version_created
|
|
})
|
|
|
|
except Exception as e:
|
|
return self.fail_response(f"Error updating agent: {str(e)}")
|
|
|
|
@openapi_schema({
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_current_agent_config",
|
|
"description": "Get the current configuration of the agent being edited. Use this to check what's already configured before making updates.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": []
|
|
}
|
|
}
|
|
})
|
|
@usage_example('''
|
|
<function_calls>
|
|
<invoke name="get_current_agent_config">
|
|
</invoke>
|
|
</function_calls>
|
|
''')
|
|
async def get_current_agent_config(self) -> ToolResult:
|
|
try:
|
|
agent_data = await self._get_agent_data()
|
|
|
|
if not agent_data:
|
|
return self.fail_response("Agent not found")
|
|
|
|
version_data = None
|
|
if agent_data.get('current_version_id'):
|
|
try:
|
|
from agent.versioning.version_service import get_version_service
|
|
account_id = await self._get_current_account_id()
|
|
version_service = await get_version_service()
|
|
version_obj = await version_service.get_version(
|
|
agent_id=self.agent_id,
|
|
version_id=agent_data['current_version_id'],
|
|
user_id=account_id
|
|
)
|
|
version_data = version_obj.to_dict()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to get version data for agent config tool: {e}")
|
|
|
|
from agent.config_helper import extract_agent_config
|
|
agent_config = extract_agent_config(agent_data, version_data)
|
|
|
|
config_summary = {
|
|
"agent_id": agent_config["agent_id"],
|
|
"name": agent_config.get("name", "Untitled Agent"),
|
|
"description": agent_config.get("description", "No description set"),
|
|
"system_prompt": agent_config.get("system_prompt", "No system prompt set"),
|
|
"avatar": agent_config.get("avatar", "🤖"),
|
|
"avatar_color": agent_config.get("avatar_color", "#6B7280"),
|
|
"agentpress_tools": agent_config.get("agentpress_tools", {}),
|
|
"configured_mcps": agent_config.get("configured_mcps", []),
|
|
"custom_mcps": agent_config.get("custom_mcps", []),
|
|
"created_at": agent_data.get("created_at"),
|
|
"updated_at": agent_data.get("updated_at"),
|
|
"current_version": agent_config.get("version_name", "v1") if version_data else "No version data"
|
|
}
|
|
|
|
enabled_tools = []
|
|
for tool_name, tool_config in config_summary["agentpress_tools"].items():
|
|
if isinstance(tool_config, bool):
|
|
if tool_config:
|
|
enabled_tools.append(tool_name)
|
|
elif isinstance(tool_config, dict):
|
|
if tool_config.get("enabled", False):
|
|
enabled_tools.append(tool_name)
|
|
tools_count = len(enabled_tools)
|
|
mcps_count = len(config_summary["configured_mcps"])
|
|
custom_mcps_count = len(config_summary["custom_mcps"])
|
|
|
|
summary_text = f"Agent '{config_summary['name']}' (version: {config_summary['current_version']}) has {tools_count} tools enabled, {mcps_count} MCP servers configured, and {custom_mcps_count} custom MCP integrations."
|
|
|
|
return self.success_response({
|
|
"summary": summary_text,
|
|
"configuration": config_summary
|
|
})
|
|
|
|
except Exception as e:
|
|
return self.fail_response(f"Error getting agent configuration: {str(e)}") |