Skip to content

Commit ae6b673

Browse files
Add file manipulation tools and TodoWrite integration to TinyAgent
This commit introduces a comprehensive suite of file manipulation tools, including read_file, write_file, update_file, glob, and grep, all designed to operate within sandboxed environments for enhanced security. Additionally, the TodoWrite tool is integrated, enabling structured task management and progress tracking. Documentation is updated to reflect these new features, providing developers with clear guidance on utilizing the tools effectively. Tests are added to ensure functionality and reliability of the new features.
1 parent c37fb07 commit ae6b673

28 files changed

Lines changed: 7518 additions & 871 deletions

README.md

Lines changed: 504 additions & 16 deletions
Large diffs are not rendered by default.

product_manager/file_manipulation_tools_roadmap.md

Lines changed: 1459 additions & 0 deletions
Large diffs are not rendered by default.

tests/test_file_tools.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Tests for TinyAgent file manipulation tools.
4+
"""
5+
6+
import asyncio
7+
import logging
8+
import sys
9+
import tempfile
10+
import os
11+
from pathlib import Path
12+
13+
# Add project root to path
14+
sys.path.append(str(Path(__file__).parent.parent))
15+
16+
# Setup logging
17+
logging.basicConfig(
18+
level=logging.INFO,
19+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
20+
handlers=[logging.StreamHandler(sys.stdout)]
21+
)
22+
logger = logging.getLogger(__name__)
23+
24+
async def test_file_tools():
25+
"""Test file manipulation tools with TinyCodeAgent."""
26+
logger.info("=== Testing File Manipulation Tools ===")
27+
28+
try:
29+
from tinyagent.code_agent import TinyCodeAgent
30+
31+
# Create a temporary directory for testing
32+
with tempfile.TemporaryDirectory() as temp_dir:
33+
logger.info(f"Using temporary directory: {temp_dir}")
34+
35+
# Create TinyCodeAgent with file tools enabled
36+
agent = TinyCodeAgent(
37+
model="gpt-4o-mini",
38+
provider="modal",
39+
local_execution=True,
40+
enable_file_tools=True,
41+
enable_python_tool=False, # Disable to focus on file tools
42+
enable_shell_tool=False, # Disable to focus on file tools
43+
system_prompt="You are a helpful assistant with file manipulation capabilities."
44+
)
45+
logger.info("✅ Created TinyCodeAgent with file tools enabled")
46+
47+
# Test 1: Write a file
48+
logger.info("=== Test 1: Write File ===")
49+
test_file = os.path.join(temp_dir, "test.txt")
50+
result = await agent.run(f'Use the write_file tool to create {test_file} with content: print("Hello, World!")')
51+
logger.info(f"Write result: {result}")
52+
53+
# Verify file was created
54+
if os.path.exists(test_file):
55+
logger.info("✅ File was created successfully")
56+
with open(test_file, 'r') as f:
57+
content = f.read()
58+
logger.info(f"File content: {content}")
59+
else:
60+
logger.error("❌ File was not created")
61+
return False
62+
63+
# Test 2: Read the file back
64+
logger.info("=== Test 2: Read File ===")
65+
result = await agent.run(f"Use the read_file tool to read {test_file}")
66+
logger.info(f"Read result: {result}")
67+
68+
# Test 3: Update the file
69+
logger.info("=== Test 3: Update File ===")
70+
result = await agent.run(f'Use the update_file tool to change "Hello" to "Hi" in {test_file}')
71+
logger.info(f"Update result: {result}")
72+
73+
# Verify update worked
74+
with open(test_file, 'r') as f:
75+
updated_content = f.read()
76+
if "Hi" in updated_content:
77+
logger.info("✅ File was updated successfully")
78+
logger.info(f"Updated content: {updated_content}")
79+
else:
80+
logger.error("❌ File update failed")
81+
logger.error(f"Content after update: {updated_content}")
82+
return False
83+
84+
# Test 4: Create another file for search
85+
logger.info("=== Test 4: Search Files ===")
86+
test_file2 = os.path.join(temp_dir, "config.py")
87+
result = await agent.run(f'Use the write_file tool to create {test_file2} with content: DEBUG = True\\nDATABASE_URL = "sqlite:///test.db"\\nSECRET_KEY = "test-key"')
88+
logger.info(f"Config file creation result: {result}")
89+
90+
result = await agent.run(f'Use the search_files tool to find files containing "DEBUG" in directory {temp_dir}')
91+
logger.info(f"Search result: {result}")
92+
93+
# Test 5: Error handling - try to read non-existent file
94+
logger.info("=== Test 5: Error Handling ===")
95+
result = await agent.run(f"Use the read_file tool to read {temp_dir}/nonexistent.txt")
96+
logger.info(f"Error handling result: {result}")
97+
98+
# Test 6: Test binary file detection
99+
logger.info("=== Test 6: Binary File Detection ===")
100+
binary_file = os.path.join(temp_dir, "test.bin")
101+
# Create a binary file
102+
with open(binary_file, 'wb') as f:
103+
f.write(b'\x00\x01\x02\x03\x04\x05')
104+
result = await agent.run(f"Use the read_file tool to read {binary_file}")
105+
logger.info(f"Binary file read result: {result}")
106+
107+
# Test 7: Test directory handling
108+
logger.info("=== Test 7: Directory Handling ===")
109+
result = await agent.run(f"Use the read_file tool to read {temp_dir}")
110+
logger.info(f"Directory read result: {result}")
111+
112+
logger.info("🎉 All file tool tests completed successfully!")
113+
return True
114+
115+
except Exception as e:
116+
logger.error(f"File tools test failed: {e}", exc_info=True)
117+
return False
118+
119+
if __name__ == "__main__":
120+
success = asyncio.run(test_file_tools())
121+
print(f"\nFile Tools Test {'PASSED' if success else 'FAILED'}")
122+
sys.exit(0 if success else 1)

tests/test_file_tools_direct.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""
2+
Direct test of file tools with actual provider to verify core functionality.
3+
"""
4+
5+
import asyncio
6+
import os
7+
import tempfile
8+
import shutil
9+
from pathlib import Path
10+
import sys
11+
12+
# Add the project root to sys.path
13+
project_root = Path(__file__).parent.parent
14+
sys.path.insert(0, str(project_root))
15+
16+
from tinyagent.code_agent.providers.modal_provider import ModalProvider
17+
from tinyagent.hooks.logging_manager import LoggingManager
18+
19+
20+
async def test_provider_file_operations_directly():
21+
"""Test provider file operations directly to verify they work."""
22+
23+
# Create temp directory for testing
24+
temp_dir = tempfile.mkdtemp()
25+
dummy_path = os.path.join(temp_dir, "dummy.txt")
26+
27+
try:
28+
print("🧪 Testing provider file operations directly...")
29+
print(f"📁 Test directory: {temp_dir}")
30+
print(f"📄 Dummy file: {dummy_path}")
31+
32+
# Create provider with local execution
33+
log_manager = LoggingManager()
34+
provider = ModalProvider(log_manager=log_manager, local_execution=True)
35+
36+
print("\n1️⃣ Testing provider write_file...")
37+
write_result = await provider.write_file(dummy_path, "Hello, World!\nThis is a test file.")
38+
print(f"✅ Write result: {write_result}")
39+
40+
if write_result.get("success"):
41+
# Verify file exists and has correct content
42+
assert os.path.exists(dummy_path), "File should exist after write"
43+
with open(dummy_path, 'r') as f:
44+
content = f.read()
45+
assert "Hello, World!" in content, "File should contain written content"
46+
print(f"✅ File created with content: {repr(content)}")
47+
48+
print("\n2️⃣ Testing provider read_file...")
49+
read_result = await provider.read_file(dummy_path)
50+
print(f"✅ Read result: {read_result}")
51+
assert read_result.get("success"), "Read should succeed"
52+
assert "Hello, World!" in read_result.get("content", ""), "Read should return file content"
53+
54+
print("\n3️⃣ Testing provider update_file...")
55+
update_result = await provider.update_file(dummy_path, "Hello, World!", "Hello, TinyAgent!")
56+
print(f"✅ Update result: {update_result}")
57+
58+
if update_result.get("success"):
59+
# Verify file was updated
60+
with open(dummy_path, 'r') as f:
61+
updated_content = f.read()
62+
assert "Hello, TinyAgent!" in updated_content, "File should contain updated content"
63+
assert "Hello, World!" not in updated_content, "Old content should be replaced"
64+
print(f"✅ File updated with content: {repr(updated_content)}")
65+
66+
print("\n4️⃣ Testing provider search_files...")
67+
search_result = await provider.search_files("TinyAgent", temp_dir)
68+
print(f"✅ Search result: {search_result}")
69+
assert search_result.get("success"), "Search should succeed"
70+
71+
print("\n🎉 All provider file operations work correctly!")
72+
return True
73+
else:
74+
print(f"❌ Write operation failed: {write_result}")
75+
return False
76+
77+
except Exception as e:
78+
print(f"❌ Test failed: {e}")
79+
import traceback
80+
traceback.print_exc()
81+
return False
82+
finally:
83+
# Clean up
84+
shutil.rmtree(temp_dir)
85+
print(f"🧹 Cleaned up test directory")
86+
87+
88+
async def test_provider_current_directory():
89+
"""Test provider file operations in current directory."""
90+
91+
current_dir = os.getcwd()
92+
dummy_path = os.path.join(current_dir, "dummy.txt")
93+
94+
try:
95+
print("\n🌍 Testing provider file operations in current directory...")
96+
print(f"📁 Current directory: {current_dir}")
97+
print(f"📄 Dummy file: {dummy_path}")
98+
99+
# Create provider with local execution
100+
log_manager = LoggingManager()
101+
provider = ModalProvider(log_manager=log_manager, local_execution=True)
102+
103+
print("\n1️⃣ Testing provider write_file in current directory...")
104+
write_result = await provider.write_file(dummy_path, "Test content from file tools\nThis verifies file tools work out of the box!")
105+
print(f"✅ Write result: {write_result}")
106+
107+
if write_result.get("success"):
108+
# Verify file exists and has correct content
109+
assert os.path.exists(dummy_path), "File should exist after write"
110+
with open(dummy_path, 'r') as f:
111+
content = f.read()
112+
assert "Test content" in content, "File should contain written content"
113+
print(f"✅ File created successfully with content: {repr(content)}")
114+
115+
print("\n2️⃣ Testing provider read_file in current directory...")
116+
read_result = await provider.read_file(dummy_path)
117+
print(f"✅ Read result: {read_result}")
118+
assert read_result.get("success"), "Read should succeed"
119+
assert "Test content" in read_result.get("content", ""), "Read should return file content"
120+
121+
print("\n🎉 Provider file operations work out of the box in current directory!")
122+
return True
123+
else:
124+
print(f"❌ Write operation failed: {write_result}")
125+
return False
126+
127+
except Exception as e:
128+
print(f"❌ Test failed: {e}")
129+
import traceback
130+
traceback.print_exc()
131+
return False
132+
finally:
133+
# Clean up
134+
if os.path.exists(dummy_path):
135+
os.remove(dummy_path)
136+
print(f"🧹 Cleaned up dummy.txt")
137+
138+
139+
async def main():
140+
"""Run all direct provider tests."""
141+
print("🚀 Starting direct provider file operations tests...")
142+
143+
# Test 1: Direct provider testing
144+
test1_success = await test_provider_file_operations_directly()
145+
146+
# Test 2: Current directory provider testing (real world scenario)
147+
test2_success = await test_provider_current_directory()
148+
149+
if test1_success and test2_success:
150+
print("\n✅ All direct provider tests passed! File operations work at the provider level.")
151+
print("🎯 Core file operations are functional ✓")
152+
else:
153+
print("\n❌ Some direct provider tests failed. File operations need fixing at the provider level.")
154+
155+
156+
if __name__ == "__main__":
157+
asyncio.run(main())

0 commit comments

Comments
 (0)