Skip to content

Commit 22bd928

Browse files
committed
Add exec_command tool, integrate it into main app, and add tests
1 parent 61cbdd1 commit 22bd928

3 files changed

Lines changed: 80 additions & 1 deletion

File tree

mini_copilot/exec_tool.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import subprocess
2+
3+
4+
def exec_command(command: str) -> str:
5+
"""Execute a shell command and return its output (stdout or stderr)."""
6+
print(f"[exec] Running command: {command}")
7+
try:
8+
# Run command with a 30s timeout, capturing both stdout and stderr
9+
result = subprocess.run(
10+
command, shell=True, capture_output=True, text=True, timeout=30
11+
)
12+
output = result.stdout if result.returncode == 0 else result.stderr
13+
if not output.strip():
14+
output = f"(Process exited with code {result.returncode})"
15+
return output
16+
except subprocess.TimeoutExpired:
17+
return "Error: Command timed out after 30 seconds."
18+
except Exception as e:
19+
return f"Error executing command: {str(e)}"
20+
21+
22+
if __name__ == "__main__":
23+
import sys
24+
25+
if len(sys.argv) > 1:
26+
print(exec_command(" ".join(sys.argv[1:])))

mini_copilot/main.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def completer(text, state):
2020

2121
from mini_copilot.github_api import chat, get_copilot_token
2222
from mini_copilot.web_search import web_search
23+
from mini_copilot.exec_tool import exec_command
2324
from mini_copilot.commands.auth import handle_login_command
2425
from mini_copilot.commands.model import handle_model_command
2526
from mini_copilot.commands.search_provider import handle_search_provider_command
@@ -59,7 +60,25 @@ def completer(text, state):
5960
},
6061
},
6162
}
62-
TOOLS = [WEB_SEARCH_TOOL]
63+
64+
EXEC_COMMAND_TOOL = {
65+
"type": "function",
66+
"function": {
67+
"name": "exec_command",
68+
"description": "Execute a shell command on the local system and return the output.",
69+
"parameters": {
70+
"type": "object",
71+
"properties": {
72+
"command": {
73+
"type": "string",
74+
"description": "The shell command to execute.",
75+
},
76+
},
77+
"required": ["command"],
78+
},
79+
},
80+
}
81+
TOOLS = [WEB_SEARCH_TOOL, EXEC_COMMAND_TOOL]
6382

6483

6584
def load_github_token():
@@ -167,6 +186,19 @@ def main():
167186
"content": search_context,
168187
}
169188
)
189+
190+
if function_name == "exec_command":
191+
command = function_args.get("command")
192+
output = exec_command(command)
193+
194+
messages.append(
195+
{
196+
"tool_call_id": tool_call["id"],
197+
"role": "tool",
198+
"name": function_name,
199+
"content": output,
200+
}
201+
)
170202
response_message = chat(
171203
messages, copilot_token, current_model, tools=TOOLS
172204
)

tests/test_exec_tool.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import unittest
2+
from mini_copilot.exec_tool import exec_command
3+
4+
5+
class TestExecTool(unittest.TestCase):
6+
def test_exec_echo(self):
7+
output = exec_command("echo 'hello world'")
8+
self.assertEqual(output.strip(), "hello world")
9+
10+
def test_exec_ls(self):
11+
output = exec_command("ls pyproject.toml")
12+
self.assertIn("pyproject.toml", output)
13+
14+
def test_exec_error(self):
15+
# Depending on the system, the error message might vary, but it should contain some indication of failure
16+
output = exec_command("non_existent_command_12345")
17+
self.assertIn("not found", output.lower())
18+
19+
20+
if __name__ == "__main__":
21+
unittest.main()

0 commit comments

Comments
 (0)