mirror of https://github.com/kortix-ai/suna.git
wip
This commit is contained in:
parent
97d58267d6
commit
b370a34ee4
|
@ -84,6 +84,8 @@ target/
|
||||||
profile_default/
|
profile_default/
|
||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
|
test/
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
|
15
README.md
15
README.md
|
@ -148,7 +148,7 @@ We welcome contributions! Feel free to:
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
1. Clone for reference:
|
1. Clone:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/kortix-ai/agentpress
|
git clone https://github.com/kortix-ai/agentpress
|
||||||
cd agentpress
|
cd agentpress
|
||||||
|
@ -160,6 +160,19 @@ pip install poetry
|
||||||
poetry install
|
poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
3. Build the package:
|
||||||
|
```bash
|
||||||
|
poetry build
|
||||||
|
```
|
||||||
|
It will return the built package name with the version number.
|
||||||
|
|
||||||
|
4. Install the package with the correct version number, here for example its 0.1.3 `agentpress-0.1.3-py3-none-any.whl`:
|
||||||
|
```bash
|
||||||
|
pip install /Users/markokraemer/Projects/agentpress/dist/agentpress-0.1.3-py3-none-any.whl --force-reinstall
|
||||||
|
```
|
||||||
|
Then you can test that version.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT License](LICENSE)
|
[MIT License](LICENSE)
|
||||||
|
|
|
@ -47,13 +47,29 @@ async def run_agent(thread_id: str, max_iterations: int = 5):
|
||||||
system_message = {
|
system_message = {
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": """
|
"content": """
|
||||||
You are a world-class web developer who can create, update, delete files, and execute terminal commands. You write clean, well-structured code. Keep iterating on existing files, continue working on this existing codebase - do not omit previous progress; instead, keep iterating.
|
You are a world-class web developer who can create, edit, and delete files, and execute terminal commands. You write clean, well-structured code. Keep iterating on existing files, continue working on this existing codebase - do not omit previous progress; instead, keep iterating.
|
||||||
|
|
||||||
|
RULES:
|
||||||
|
- All current file contents are available to you in the <current_workspace_state> section
|
||||||
|
- Each file in the workspace state includes:
|
||||||
|
* content: The full file contents
|
||||||
|
* line_count: Total number of lines in the file
|
||||||
|
* lines: Array of line objects containing:
|
||||||
|
- number: Line number (1-based)
|
||||||
|
- content: The line's content
|
||||||
|
- length: Length of the line
|
||||||
|
- Use str_replace for precise replacements in files
|
||||||
|
- Use insert_lines to add new content at specific line numbers (use the line numbers from the workspace state)
|
||||||
|
- NEVER include comments in any code you write - the code should be self-documenting
|
||||||
|
- Always maintain the full context of files when making changes
|
||||||
|
- When creating new files, write clean code without any comments or documentation
|
||||||
|
|
||||||
<available_tools>
|
<available_tools>
|
||||||
[update_file(file_path, file_contents)]
|
[create_file(file_path, file_contents)] - Create new files
|
||||||
[create_file(file_path, file_contents)]
|
[delete_file(file_path)] - Delete existing files
|
||||||
[delete_file(file_path)]
|
[str_replace(file_path, old_str, new_str)] - Replace specific text in files
|
||||||
[execute_command(command)]
|
[insert_lines(file_path, insert_line, new_content)] - Insert content at specific line number
|
||||||
|
[execute_command(command)] - Execute terminal commands
|
||||||
</available_tools>
|
</available_tools>
|
||||||
|
|
||||||
ALWAYS RESPOND WITH MULTIPLE SIMULTANEOUS ACTIONS:
|
ALWAYS RESPOND WITH MULTIPLE SIMULTANEOUS ACTIONS:
|
||||||
|
@ -65,6 +81,25 @@ ALWAYS RESPOND WITH MULTIPLE SIMULTANEOUS ACTIONS:
|
||||||
[Include multiple tool calls]
|
[Include multiple tool calls]
|
||||||
</actions>
|
</actions>
|
||||||
|
|
||||||
|
EDITING GUIDELINES:
|
||||||
|
1. Review the current file contents and line information in the workspace state
|
||||||
|
2. Use line numbers from the workspace state for precise insertions
|
||||||
|
3. Make targeted changes with str_replace or insert_lines
|
||||||
|
4. Write clean, self-documenting code without comments
|
||||||
|
|
||||||
|
Example workspace state for a file:
|
||||||
|
{
|
||||||
|
"index.html": {
|
||||||
|
"content": "<!DOCTYPE html>\\n<html>\\n<head>...",
|
||||||
|
"line_count": 15,
|
||||||
|
"lines": [
|
||||||
|
{"number": 1, "content": "<!DOCTYPE html>", "length": 15},
|
||||||
|
{"number": 2, "content": "<html>", "length": 6},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Think deeply and step by step.
|
Think deeply and step by step.
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime
|
from pathlib import Path
|
||||||
from agentpress.tool import Tool, ToolResult, tool_schema
|
from agentpress.tool import Tool, ToolResult, tool_schema
|
||||||
from agentpress.state_manager import StateManager
|
from agentpress.state_manager import StateManager
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ class FilesTool(Tool):
|
||||||
self.workspace = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'workspace')
|
self.workspace = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'workspace')
|
||||||
os.makedirs(self.workspace, exist_ok=True)
|
os.makedirs(self.workspace, exist_ok=True)
|
||||||
self.state_manager = StateManager("state.json")
|
self.state_manager = StateManager("state.json")
|
||||||
|
self.SNIPPET_LINES = 4 # Number of context lines to show around edits
|
||||||
asyncio.create_task(self._init_workspace_state())
|
asyncio.create_task(self._init_workspace_state())
|
||||||
|
|
||||||
def _should_exclude_file(self, rel_path: str) -> bool:
|
def _should_exclude_file(self, rel_path: str) -> bool:
|
||||||
|
@ -83,7 +84,21 @@ class FilesTool(Tool):
|
||||||
try:
|
try:
|
||||||
with open(full_path, 'r') as f:
|
with open(full_path, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
files_state[rel_path] = content
|
lines = content.split('\n')
|
||||||
|
|
||||||
|
# Create file state object with content and line information
|
||||||
|
files_state[rel_path] = {
|
||||||
|
"content": content,
|
||||||
|
"line_count": len(lines),
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"number": i + 1,
|
||||||
|
"content": line,
|
||||||
|
"length": len(line)
|
||||||
|
}
|
||||||
|
for i, line in enumerate(lines)
|
||||||
|
]
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error reading file {rel_path}: {e}")
|
print(f"Error reading file {rel_path}: {e}")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
|
@ -122,29 +137,6 @@ class FilesTool(Tool):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self.fail_response(f"Error creating file: {str(e)}")
|
return self.fail_response(f"Error creating file: {str(e)}")
|
||||||
|
|
||||||
@tool_schema({
|
|
||||||
"name": "update_file",
|
|
||||||
"description": "Update an existing file at the given path in the workspace with the provided contents.",
|
|
||||||
"parameters": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"file_path": {"type": "string", "description": "Path to the file to be updated"},
|
|
||||||
"content": {"type": "string", "description": "New content to be written to the file. ONLY CODE. The whole file contents, the complete code – The contents of the new file with all instructions implemented perfectly. NEVER write comments. Keep the complete File Contents within this key."}
|
|
||||||
},
|
|
||||||
"required": ["file_path", "content"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
async def update_file(self, file_path: str, content: str) -> ToolResult:
|
|
||||||
try:
|
|
||||||
full_path = os.path.join(self.workspace, file_path)
|
|
||||||
with open(full_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
await self._update_workspace_state()
|
|
||||||
return self.success_response(f"File '{file_path}' updated successfully.")
|
|
||||||
except Exception as e:
|
|
||||||
return self.fail_response(f"Error updating file: {str(e)}")
|
|
||||||
|
|
||||||
@tool_schema({
|
@tool_schema({
|
||||||
"name": "delete_file",
|
"name": "delete_file",
|
||||||
"description": "Delete a file at the given path in the workspace.",
|
"description": "Delete a file at the given path in the workspace.",
|
||||||
|
@ -166,6 +158,93 @@ class FilesTool(Tool):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self.fail_response(f"Error deleting file: {str(e)}")
|
return self.fail_response(f"Error deleting file: {str(e)}")
|
||||||
|
|
||||||
|
@tool_schema({
|
||||||
|
"name": "str_replace",
|
||||||
|
"description": "Replace a string with another string in a file",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {"type": "string", "description": "Path to the file"},
|
||||||
|
"old_str": {"type": "string", "description": "String to replace"},
|
||||||
|
"new_str": {"type": "string", "description": "Replacement string"}
|
||||||
|
},
|
||||||
|
"required": ["file_path", "old_str", "new_str"]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
async def str_replace(self, file_path: str, old_str: str, new_str: str) -> ToolResult:
|
||||||
|
try:
|
||||||
|
full_path = Path(os.path.join(self.workspace, file_path))
|
||||||
|
if not full_path.exists():
|
||||||
|
return self.fail_response(f"File '{file_path}' does not exist")
|
||||||
|
|
||||||
|
content = full_path.read_text().expandtabs()
|
||||||
|
old_str = old_str.expandtabs()
|
||||||
|
new_str = new_str.expandtabs()
|
||||||
|
|
||||||
|
occurrences = content.count(old_str)
|
||||||
|
if occurrences == 0:
|
||||||
|
return self.fail_response(f"String '{old_str}' not found in file")
|
||||||
|
if occurrences > 1:
|
||||||
|
lines = [i+1 for i, line in enumerate(content.split('\n')) if old_str in line]
|
||||||
|
return self.fail_response(f"Multiple occurrences found in lines {lines}. Please ensure string is unique")
|
||||||
|
|
||||||
|
# Perform replacement
|
||||||
|
new_content = content.replace(old_str, new_str)
|
||||||
|
full_path.write_text(new_content)
|
||||||
|
|
||||||
|
# Show snippet around the edit
|
||||||
|
replacement_line = content.split(old_str)[0].count('\n')
|
||||||
|
start_line = max(0, replacement_line - self.SNIPPET_LINES)
|
||||||
|
end_line = replacement_line + self.SNIPPET_LINES + new_str.count('\n')
|
||||||
|
snippet = '\n'.join(new_content.split('\n')[start_line:end_line + 1])
|
||||||
|
|
||||||
|
return self.success_response(f"Replacement successful. Snippet of changes:\n{snippet}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return self.fail_response(f"Error replacing string: {str(e)}")
|
||||||
|
|
||||||
|
@tool_schema({
|
||||||
|
"name": "insert_lines",
|
||||||
|
"description": "Insert lines at a specific line number in a file",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"file_path": {"type": "string", "description": "Path to the file"},
|
||||||
|
"insert_line": {"type": "integer", "description": "Line number to insert at"},
|
||||||
|
"new_content": {"type": "string", "description": "Content to insert"}
|
||||||
|
},
|
||||||
|
"required": ["file_path", "insert_line", "new_content"]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
async def insert_lines(self, file_path: str, insert_line: int, new_content: str) -> ToolResult:
|
||||||
|
try:
|
||||||
|
full_path = Path(os.path.join(self.workspace, file_path))
|
||||||
|
if not full_path.exists():
|
||||||
|
return self.fail_response(f"File '{file_path}' does not exist")
|
||||||
|
|
||||||
|
content = full_path.read_text().expandtabs()
|
||||||
|
lines = content.split('\n')
|
||||||
|
|
||||||
|
if insert_line < 0 or insert_line > len(lines):
|
||||||
|
return self.fail_response(f"Invalid line number {insert_line}")
|
||||||
|
|
||||||
|
# Insert new content
|
||||||
|
new_lines = new_content.expandtabs().split('\n')
|
||||||
|
updated_lines = lines[:insert_line] + new_lines + lines[insert_line:]
|
||||||
|
new_content = '\n'.join(updated_lines)
|
||||||
|
|
||||||
|
full_path.write_text(new_content)
|
||||||
|
|
||||||
|
# Show snippet around the edit
|
||||||
|
start_line = max(0, insert_line - self.SNIPPET_LINES)
|
||||||
|
end_line = insert_line + len(new_lines) + self.SNIPPET_LINES
|
||||||
|
snippet = '\n'.join(updated_lines[start_line:end_line])
|
||||||
|
|
||||||
|
return self.success_response(f"Lines inserted successfully. Snippet of changes:\n{snippet}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return self.fail_response(f"Error inserting lines: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
async def test_files_tool():
|
async def test_files_tool():
|
||||||
|
@ -180,10 +259,6 @@ if __name__ == "__main__":
|
||||||
create_result = await files_tool.create_file(test_file_path, test_content)
|
create_result = await files_tool.create_file(test_file_path, test_content)
|
||||||
print("Create file result:", create_result)
|
print("Create file result:", create_result)
|
||||||
|
|
||||||
# Test update_file
|
|
||||||
update_result = await files_tool.update_file(test_file_path, updated_content)
|
|
||||||
print("Update file result:", update_result)
|
|
||||||
|
|
||||||
# Test delete_file
|
# Test delete_file
|
||||||
delete_result = await files_tool.delete_file(test_file_path)
|
delete_result = await files_tool.delete_file(test_file_path)
|
||||||
print("Delete file result:", delete_result)
|
print("Delete file result:", delete_result)
|
||||||
|
|
Loading…
Reference in New Issue