diff --git a/integration_tests/test_edit_integration.py b/integration_tests/test_edit_integration.py new file mode 100644 index 0000000..f53a724 --- /dev/null +++ b/integration_tests/test_edit_integration.py @@ -0,0 +1,102 @@ +import subprocess +import time +import sys +import os + + +def run_integration_test(): + print("šŸš€ Starting Integration Test for 'edit' tool...") + + # Ensure current directory is in PYTHONPATH for the subprocess + env = os.environ.copy() + env["PYTHONPATH"] = os.getcwd() + ":" + env.get("PYTHONPATH", "") + + # Create a test file + test_filename = "test_edit_integration.txt" + with open(test_filename, "w") as f: + f.write("Line 1\nLine 2\nLine 3\n") + + # Start the mini-copilot process + process = subprocess.Popen( + [sys.executable, "-m", "mini_copilot.main"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + cwd=os.getcwd(), + env=env, + ) + + try: + # 1. Wait for prompt + print("Waiting for prompt...") + output = "" + start_time = time.time() + while time.time() - start_time < 30: + char = process.stdout.read(1) + if not char: + break + output += char + if "> " in output: + print("āœ… Prompt detected.") + break + + if "> " not in output: + print(f"āŒ Timed out waiting for prompt. Last output: {output}") + return False + + # 2. Trigger tool + print(f"Sending message to trigger 'edit' tool on {test_filename}...") + edit_request = f"Edit the file {test_filename}. Change 'Line 2' to 'Line Two'.\n" + process.stdin.write(edit_request) + process.stdin.flush() + + # 3. Look for tool execution + print("Monitoring for tool invocation and response...") + found_tool = False + found_success = False + start_time = time.time() + output = "" + while time.time() - start_time < 60: + char = process.stdout.read(1) + if not char: + break + output += char + sys.stdout.write(char) + sys.stdout.flush() + + if f"Successfully edited {test_filename}" in output: + found_tool = True + + # If the assistant replies and we found the tool message, we are good + if found_tool and "> " in output: + found_success = True + break + + if found_tool: + # Verify file content + with open(test_filename, "r") as f: + content = f.read() + if "Line Two" in content and "Line 2" not in content: + print(f"\nāœ… File content verified: {test_filename}") + print("\nšŸŽ‰ Integration Test PASSED!") + return True + else: + print(f"\nāŒ File content mismatch. Content: {content}") + return False + else: + print("\nāŒ Integration Test FAILED (Tool not triggered or response not found).") + return False + + finally: + process.terminate() + if os.path.exists(test_filename): + os.remove(test_filename) + + +if __name__ == "__main__": + if run_integration_test(): + sys.exit(0) + else: + sys.exit(1) diff --git a/mini_copilot/main.py b/mini_copilot/main.py index 18331da..3b4b950 100644 --- a/mini_copilot/main.py +++ b/mini_copilot/main.py @@ -21,6 +21,7 @@ def completer(text, state): from mini_copilot.github_api import chat, get_copilot_token from mini_copilot.web_search import web_search from mini_copilot.exec_tool import exec_command as exec +from tools.edit_tool import EditTool from mini_copilot.commands.auth import handle_login_command from mini_copilot.commands.model import handle_model_command from mini_copilot.commands.search_provider import handle_search_provider_command @@ -78,7 +79,29 @@ def completer(text, state): }, }, } -TOOLS = [WEB_SEARCH_TOOL, EXEC_COMMAND_TOOL] + +EDIT_TOOL = { + "type": "function", + "function": { + "name": "edit", + "description": "Apply a unified diff edit to a file on the local system.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "The path to the file to edit.", + }, + "edit_content": { + "type": "string", + "description": "The unified diff content to apply.", + }, + }, + "required": ["file_path", "edit_content"], + }, + }, +} +TOOLS = [WEB_SEARCH_TOOL, EXEC_COMMAND_TOOL, EDIT_TOOL] def load_github_token(): @@ -199,6 +222,23 @@ def main(): "content": output, } ) + + if function_name == "edit": + file_path = function_args.get("file_path") + edit_content = function_args.get("edit_content") + + result = EditTool.edit(file_path, edit_content) + with open(file_path, "w") as f: + f.write(result) + + messages.append( + { + "tool_call_id": tool_call["id"], + "role": "tool", + "name": function_name, + "content": f"Successfully edited {file_path}", + } + ) response_message = chat( messages, copilot_token, current_model, tools=TOOLS )