mirror of https://github.com/kortix-ai/suna.git
wip
This commit is contained in:
parent
1e42e639b0
commit
cefddbeae9
52
agent.py
52
agent.py
|
@ -2,7 +2,8 @@ import asyncio
|
|||
import json
|
||||
from agentpress.thread_manager import ThreadManager
|
||||
from tools.files_tool import FilesTool
|
||||
from state_manager import StateManager
|
||||
from agentpress.state_manager import StateManager
|
||||
from tools.terminal_tool import TerminalTool
|
||||
|
||||
async def run_agent(
|
||||
thread_manager: ThreadManager,
|
||||
|
@ -10,16 +11,22 @@ async def run_agent(
|
|||
state_manager: StateManager,
|
||||
max_iterations: int = 10
|
||||
):
|
||||
|
||||
async def init():
|
||||
pass
|
||||
|
||||
async def pre_iteration():
|
||||
pass
|
||||
# Update files state
|
||||
files_tool = FilesTool()
|
||||
await files_tool._init_workspace_state()
|
||||
|
||||
terminal_tool = TerminalTool()
|
||||
await terminal_tool.get_command_history()
|
||||
|
||||
async def after_iteration():
|
||||
await thread_manager.add_message(thread_id, {"role": "user", "content": "CREATE MORE RANDOM FILES WITH RANDOM CONTENTS. JSUT CREATE IT – NO QUESTINS PLEASE.'"})
|
||||
pass
|
||||
await thread_manager.add_message(thread_id, {
|
||||
"role": "user",
|
||||
"content": "Continue developing. "
|
||||
})
|
||||
|
||||
async def finalizer():
|
||||
pass
|
||||
|
@ -33,28 +40,27 @@ async def run_agent(
|
|||
|
||||
# Get entire state store
|
||||
state = await state_manager.export_store()
|
||||
state_info = f"Current state store:\n{json.dumps(state, indent=2)}"
|
||||
state_info = f"Current workspace state:\n{json.dumps(state, indent=2)}"
|
||||
|
||||
system_message = {
|
||||
"role": "system",
|
||||
"content": f"You are a helpful assistant that can create, read, update, and delete files.\n\n{state_info}"
|
||||
}
|
||||
model_name = "gpt-4o"
|
||||
"content": f"""You are a web developer who can create, read, update, and delete files,
|
||||
and execute terminal commands. You write clean, well-structured code and explain your changes.
|
||||
|
||||
# Include entire state in additional message
|
||||
# additional_message = {
|
||||
# "role": "user",
|
||||
# "content": state_info
|
||||
# }
|
||||
Current workspace state:
|
||||
{state_info}
|
||||
|
||||
Explain what you're doing before making changes."""
|
||||
}
|
||||
model_name = "anthropic/claude-3-5-sonnet-latest"
|
||||
|
||||
response = await thread_manager.run_thread(
|
||||
thread_id=thread_id,
|
||||
system_message=system_message,
|
||||
model_name=model_name,
|
||||
temperature=0.7,
|
||||
max_tokens=150,
|
||||
max_tokens=4096,
|
||||
tool_choice="auto",
|
||||
# additional_message=additional_message,
|
||||
execute_tools_async=False,
|
||||
use_tools=True,
|
||||
execute_model_tool_calls=True
|
||||
|
@ -71,12 +77,16 @@ if __name__ == "__main__":
|
|||
state_manager = StateManager("state.json")
|
||||
thread_id = await thread_manager.create_thread()
|
||||
|
||||
# Read current file contents
|
||||
files = await state_manager.get("files")
|
||||
|
||||
await thread_manager.add_message(thread_id, {"role": "user", "content": "Please create a file with a random name with the content 'Hello, world!'"})
|
||||
|
||||
thread_manager.add_tool(FilesTool)
|
||||
thread_manager.add_tool(TerminalTool)
|
||||
|
||||
await thread_manager.add_message(
|
||||
thread_id,
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Let's create a marketing website."
|
||||
}
|
||||
)
|
||||
|
||||
await run_agent(
|
||||
thread_manager=thread_manager,
|
||||
|
|
50
state.json
50
state.json
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"messages": [{"role": "user", "content": "Let's develop a simple web application. Start by:\n 1. Creating a basic project structure\n 2. Setting up main application files\n 3. Adding necessary configuration files\n \n Use modern best practices and explain your decisions."}]}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"messages": [{"role": "user", "content": "Let's create a modern web application using Next.js 14. Start by:\n\n 1. Setting up the Next.js 14 project:\n - Initialize with TypeScript and Tailwind CSS\n - Set up proper project structure using App Router\n - Configure development environment\n \n 2. Creating core components:\n - Layout component with modern design\n - Navigation bar with mobile responsiveness\n - Hero section with animations\n - Feature sections with grid layouts\n \n 3. Implementing key features:\n - Server components for static content\n - Client components for interactive elements\n - API routes for data handling\n - Loading and error states\n \n 4. Adding modern UI elements:\n - Smooth animations and transitions\n - Dark mode support\n - Responsive design patterns\n - Accessibility features\n \n Start with project initialization and explain each step.\n Use terminal commands for setup and file operations for development."}, {"role": "assistant", "content": "I'll help you create a modern Next.js 14 application with all the requested features. Let's start with the project initialization and go through this systematically.\n\n1. First, let's create the Next.js project with TypeScript and Tailwind CSS:", "tool_calls": [{"id": "toolu_01YDfPsRz35Bs8ui9HRZ6GDu", "type": "function", "function": {"name": "execute_command", "arguments": "{\"command\": \"npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --import-alias \\\"@/*\\\"\"}"}}]}]}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"messages": [{"role": "user", "content": "Let's create a modern web application using Next.js 14. Start by:\n\n 1. Setting up the Next.js 14 project:\n - Initialize with TypeScript and Tailwind CSS\n - Set up proper project structure using App Router\n - Configure development environment\n \n 2. Creating core components:\n - Layout component with modern design\n - Navigation bar with mobile responsiveness\n - Hero section with animations\n - Feature sections with grid layouts\n \n 3. Implementing key features:\n - Server components for static content\n - Client components for interactive elements\n - API routes for data handling\n - Loading and error states\n \n 4. Adding modern UI elements:\n - Smooth animations and transitions\n - Dark mode support\n - Responsive design patterns\n - Accessibility features\n \n Start with project initialization and explain each step.\n Use terminal commands for setup and file operations for development."}, {"role": "assistant", "content": "I'll help you create a modern Next.js 14 application with all the requested features. Let's go through this step by step.\n\n1. First, let's initialize the Next.js 14 project with TypeScript and Tailwind CSS:", "tool_calls": [{"id": "toolu_01GzLWzxGNi2FSWkxGvuV8qh", "type": "function", "function": {"name": "execute_command", "arguments": "{\"command\": \"npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --import-alias \\\"@/*\\\"\", \"inputs\": [\"y\"]}"}}]}]}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"messages": [{"role": "user", "content": "Let's create a modern web application using Next.js 14. Start by:\n\n 1. Setting up the Next.js 14 project:\n - Initialize with TypeScript and Tailwind CSS\n - Set up proper project structure using App Router\n - Configure development environment\n \n 2. Creating core components:\n - Layout component with modern design\n - Navigation bar with mobile responsiveness\n - Hero section with animations\n - Feature sections with grid layouts\n \n 3. Implementing key features:\n - Server components for static content\n - Client components for interactive elements\n - API routes for data handling\n - Loading and error states\n \n 4. Adding modern UI elements:\n - Smooth animations and transitions\n - Dark mode support\n - Responsive design patterns\n - Accessibility features\n \n Start with project initialization and explain each step.\n Use terminal commands for setup and file operations for development."}]}
|
|
@ -0,0 +1 @@
|
|||
{"messages": [{"role": "user", "content": "Let's create a modern web application using Next.js 14. Start by:\n\n 1. Setting up the Next.js 14 project:\n - Initialize with TypeScript and Tailwind CSS\n - Set up proper project structure using App Router\n - Configure development environment\n \n 2. Creating core components:\n - Layout component with modern design\n - Navigation bar with mobile responsiveness\n - Hero section with animations\n - Feature sections with grid layouts\n \n 3. Implementing key features:\n - Server components for static content\n - Client components for interactive elements\n - API routes for data handling\n - Loading and error states\n \n 4. Adding modern UI elements:\n - Smooth animations and transitions\n - Dark mode support\n - Responsive design patterns\n - Accessibility features\n \n Start with project initialization and explain each step.\n Use terminal commands for setup and file operations for development."}]}
|
|
@ -0,0 +1 @@
|
|||
{"messages": [{"role": "user", "content": "Let's create a modern web application using Next.js 14. Start by:\n\n 1. Setting up the Next.js 14 project:\n - Initialize with TypeScript and Tailwind CSS\n - Set up proper project structure using App Router\n - Configure development environment\n \n 2. Creating core components:\n - Layout component with modern design\n - Navigation bar with mobile responsiveness\n - Hero section with animations\n - Feature sections with grid layouts\n \n 3. Implementing key features:\n - Server components for static content\n - Client components for interactive elements\n - API routes for data handling\n - Loading and error states\n \n 4. Adding modern UI elements:\n - Smooth animations and transitions\n - Dark mode support\n - Responsive design patterns\n - Accessibility features\n \n Start with project initialization and explain each step.\n Use terminal commands for setup and file operations for development."}, {"role": "assistant", "content": "I'll help you create a modern Next.js 14 application with all the requested features. Let's start with the project initialization and go through this systematically.\n\n1. First, let's initialize the Next.js 14 project with TypeScript and Tailwind CSS using the create-next-app command:", "tool_calls": [{"id": "toolu_014iKnSKrCuGSofVRvV2gDzZ", "type": "function", "function": {"name": "execute_command", "arguments": "{\"command\": \"npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --use-npm\"}"}}]}]}
|
|
@ -0,0 +1 @@
|
|||
{"messages": [{"role": "user", "content": "Let's create a modern web application using Flask and Tailwind CSS. Start by:\n\n 1. Setting up a Flask project structure:\n - Create requirements.txt with Flask and other dependencies\n - Set up a proper Flask application structure\n - Initialize a virtual environment\n \n 2. Integrating Tailwind CSS:\n - Add Tailwind CSS to the project\n - Set up build process for CSS\n - Create base templates with Tailwind\n \n 3. Creating basic features:\n - Homepage with modern design\n - Navigation bar\n - Responsive layout\n - Sample content section\n \n 4. Implementing best practices:\n - Use Flask blueprints\n - Set up proper configuration\n - Add error handling\n - Include development tools\n \n Start with the project setup and explain each step.\n You can use both file operations and terminal commands to set this up."}, {"role": "assistant", "content": "I'll help you create a modern Flask application with Tailwind CSS. I'll explain each step and implement them systematically.\n\n1. First, let's create the basic project structure and requirements:\n\n```\nproject_structure/\n\u251c\u2500\u2500 app/\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u251c\u2500\u2500 routes/\n\u2502 \u251c\u2500\u2500 templates/\n\u2502 \u251c\u2500\u2500 static/\n\u2502 \u2514\u2500\u2500 config.py\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 .env\n\u251c\u2500\u2500 .gitignore\n\u2514\u2500\u2500 run.py\n```\n\nLet's start by creating these files:", "tool_calls": [{"id": "toolu_01S3KBnyK3DL3oAG8vSgB9WL", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\": \"requirements.txt\", \"content\": \"Flask==2.3.3\\npython-dotenv==1.0.0\\nFlask-WTF==1.1.1\\nWerkzeug==2.3.7\\nflask-assets==2.0\\npytest==7.4.2\\npython-dotenv==1.0.0\"}"}}]}, {"role": "tool", "tool_call_id": "toolu_01S3KBnyK3DL3oAG8vSgB9WL", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'requirements.txt' created successfully.\")"}, {"role": "user", "content": "Continue developing the Flask application. Add more features, improve the UI, or refactor the code as needed."}]}
|
|
@ -2,9 +2,55 @@ import os
|
|||
import asyncio
|
||||
from datetime import datetime
|
||||
from agentpress.tool import Tool, ToolResult, tool_schema
|
||||
from state_manager import StateManager
|
||||
from agentpress.state_manager import StateManager
|
||||
|
||||
class FilesTool(Tool):
|
||||
# Excluded files, directories, and extensions
|
||||
EXCLUDED_FILES = {
|
||||
".DS_Store",
|
||||
".gitignore",
|
||||
"package-lock.json",
|
||||
"postcss.config.js",
|
||||
"postcss.config.mjs",
|
||||
"playwright.config.js",
|
||||
"jsconfig.json",
|
||||
"components.json",
|
||||
"tsconfig.tsbuildinfo",
|
||||
"next-env.d.ts",
|
||||
"tsconfig.json",
|
||||
"firebase-service-account.json",
|
||||
"Dockerfile"
|
||||
}
|
||||
|
||||
EXCLUDED_DIRS = {
|
||||
"src/components/ui",
|
||||
"cypress",
|
||||
"node_modules",
|
||||
"migrations",
|
||||
".next",
|
||||
"playwright-report",
|
||||
"test-results",
|
||||
"dist",
|
||||
"build",
|
||||
"coverage",
|
||||
"terminal_logs",
|
||||
".git"
|
||||
}
|
||||
|
||||
EXCLUDED_EXT = {
|
||||
".ico",
|
||||
".svg",
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".bmp",
|
||||
".tiff",
|
||||
".webp",
|
||||
".db",
|
||||
".sql"
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.workspace = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'workspace')
|
||||
|
@ -12,6 +58,25 @@ class FilesTool(Tool):
|
|||
self.state_manager = StateManager("state.json")
|
||||
asyncio.create_task(self._init_workspace_state())
|
||||
|
||||
def _should_exclude_file(self, rel_path: str) -> bool:
|
||||
"""Check if a file should be excluded based on path, name, or extension"""
|
||||
# Check filename
|
||||
filename = os.path.basename(rel_path)
|
||||
if filename in self.EXCLUDED_FILES:
|
||||
return True
|
||||
|
||||
# Check directory
|
||||
dir_path = os.path.dirname(rel_path)
|
||||
if any(excluded in dir_path for excluded in self.EXCLUDED_DIRS):
|
||||
return True
|
||||
|
||||
# Check extension
|
||||
_, ext = os.path.splitext(filename)
|
||||
if ext.lower() in self.EXCLUDED_EXT:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def _init_workspace_state(self):
|
||||
"""Initialize or update the workspace state in JSON"""
|
||||
files_state = {}
|
||||
|
@ -20,14 +85,20 @@ class FilesTool(Tool):
|
|||
for root, _, files in os.walk(self.workspace):
|
||||
for file in files:
|
||||
full_path = os.path.join(root, file)
|
||||
# Get path relative to workspace: e.g. "src/components/file.jsx"
|
||||
rel_path = os.path.relpath(full_path, self.workspace)
|
||||
|
||||
# Skip excluded files
|
||||
if self._should_exclude_file(rel_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(full_path, 'r') as f:
|
||||
content = f.read()
|
||||
files_state[rel_path] = content
|
||||
except Exception as e:
|
||||
print(f"Error reading file {rel_path}: {e}")
|
||||
except UnicodeDecodeError:
|
||||
print(f"Skipping binary file: {rel_path}")
|
||||
|
||||
await self.state_manager.set("files", files_state)
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import os
|
||||
import asyncio
|
||||
import subprocess
|
||||
from agentpress.tool import Tool, ToolResult, tool_schema
|
||||
from agentpress.state_manager import StateManager
|
||||
|
||||
class TerminalTool(Tool):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.workspace = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'workspace')
|
||||
os.makedirs(self.workspace, exist_ok=True)
|
||||
self.state_manager = StateManager("state.json")
|
||||
|
||||
async def _update_command_history(self, command: str, output: str, success: bool):
|
||||
"""Update command history in state"""
|
||||
history = await self.state_manager.get("terminal_history") or []
|
||||
history.append({
|
||||
"command": command,
|
||||
"output": output,
|
||||
"success": success,
|
||||
"cwd": os.path.relpath(os.getcwd(), self.workspace)
|
||||
})
|
||||
await self.state_manager.set("terminal_history", history)
|
||||
|
||||
@tool_schema({
|
||||
"name": "execute_command",
|
||||
"description": "Execute a shell command in the workspace directory",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {"type": "string", "description": "The command to execute"},
|
||||
},
|
||||
"required": ["command"]
|
||||
}
|
||||
})
|
||||
async def execute_command(self, command: str) -> ToolResult:
|
||||
original_dir = os.getcwd()
|
||||
try:
|
||||
# Always change to workspace directory before executing
|
||||
os.chdir(self.workspace)
|
||||
|
||||
# Execute command
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=self.workspace # Explicitly set working directory
|
||||
)
|
||||
stdout, stderr = await process.communicate()
|
||||
|
||||
# Prepare output
|
||||
output = stdout.decode() if stdout else ""
|
||||
error = stderr.decode() if stderr else ""
|
||||
success = process.returncode == 0
|
||||
|
||||
# Update state with command history
|
||||
await self._update_command_history(
|
||||
command=command,
|
||||
output=output + error,
|
||||
success=success
|
||||
)
|
||||
|
||||
if success:
|
||||
return self.success_response({
|
||||
"output": output,
|
||||
"error": error,
|
||||
"exit_code": process.returncode,
|
||||
"cwd": self.workspace
|
||||
})
|
||||
else:
|
||||
return self.fail_response(f"Command failed with exit code {process.returncode}: {error}")
|
||||
|
||||
except Exception as e:
|
||||
await self._update_command_history(
|
||||
command=command,
|
||||
output=str(e),
|
||||
success=False
|
||||
)
|
||||
return self.fail_response(f"Error executing command: {str(e)}")
|
||||
finally:
|
||||
# Always restore original directory
|
||||
os.chdir(original_dir)
|
||||
|
||||
@tool_schema({
|
||||
"name": "get_command_history",
|
||||
"description": "Get the history of executed commands",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {"type": "integer", "description": "Number of recent commands to return (optional)"}
|
||||
}
|
||||
}
|
||||
})
|
||||
async def get_command_history(self, limit: int = None) -> ToolResult:
|
||||
try:
|
||||
history = await self.state_manager.get("terminal_history") or []
|
||||
if limit:
|
||||
history = history[-limit:]
|
||||
return self.success_response({"history": history})
|
||||
except Exception as e:
|
||||
return self.fail_response(f"Error getting command history: {str(e)}")
|
Loading…
Reference in New Issue