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/
|
||||
ipython_config.py
|
||||
|
||||
test/
|
||||
|
||||
# pyenv
|
||||
# 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:
|
||||
|
|
15
README.md
15
README.md
|
@ -148,7 +148,7 @@ We welcome contributions! Feel free to:
|
|||
|
||||
## Development
|
||||
|
||||
1. Clone for reference:
|
||||
1. Clone:
|
||||
```bash
|
||||
git clone https://github.com/kortix-ai/agentpress
|
||||
cd agentpress
|
||||
|
@ -160,6 +160,19 @@ pip install poetry
|
|||
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
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
|
|
@ -47,13 +47,29 @@ async def run_agent(thread_id: str, max_iterations: int = 5):
|
|||
system_message = {
|
||||
"role": "system",
|
||||
"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>
|
||||
[update_file(file_path, file_contents)]
|
||||
[create_file(file_path, file_contents)]
|
||||
[delete_file(file_path)]
|
||||
[execute_command(command)]
|
||||
[create_file(file_path, file_contents)] - Create new files
|
||||
[delete_file(file_path)] - Delete existing files
|
||||
[str_replace(file_path, old_str, new_str)] - Replace specific text in files
|
||||
[insert_lines(file_path, insert_line, new_content)] - Insert content at specific line number
|
||||
[execute_command(command)] - Execute terminal commands
|
||||
</available_tools>
|
||||
|
||||
ALWAYS RESPOND WITH MULTIPLE SIMULTANEOUS ACTIONS:
|
||||
|
@ -65,6 +81,25 @@ ALWAYS RESPOND WITH MULTIPLE SIMULTANEOUS ACTIONS:
|
|||
[Include multiple tool calls]
|
||||
</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.
|
||||
"""
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from agentpress.tool import Tool, ToolResult, tool_schema
|
||||
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')
|
||||
os.makedirs(self.workspace, exist_ok=True)
|
||||
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())
|
||||
|
||||
def _should_exclude_file(self, rel_path: str) -> bool:
|
||||
|
@ -83,7 +84,21 @@ class FilesTool(Tool):
|
|||
try:
|
||||
with open(full_path, 'r') as f:
|
||||
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:
|
||||
print(f"Error reading file {rel_path}: {e}")
|
||||
except UnicodeDecodeError:
|
||||
|
@ -122,29 +137,6 @@ class FilesTool(Tool):
|
|||
except Exception as 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({
|
||||
"name": "delete_file",
|
||||
"description": "Delete a file at the given path in the workspace.",
|
||||
|
@ -166,6 +158,93 @@ class FilesTool(Tool):
|
|||
except Exception as 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__":
|
||||
async def test_files_tool():
|
||||
|
@ -180,10 +259,6 @@ if __name__ == "__main__":
|
|||
create_result = await files_tool.create_file(test_file_path, test_content)
|
||||
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
|
||||
delete_result = await files_tool.delete_file(test_file_path)
|
||||
print("Delete file result:", delete_result)
|
||||
|
|
Loading…
Reference in New Issue