PikoAi 0.1.6__py3-none-any.whl → 0.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Agents/Executor/executor.py +46 -102
- Agents/Executor/prompts.py +5 -21
- Env/base_env.py +13 -8
- Env/python_executor.py +70 -30
- Env/shell.py +141 -14
- Env/tests/__init__.py +0 -0
- Env/tests/test_python_executor.py +70 -0
- Env/tests/test_shell_executor.py +29 -0
- OpenCopilot.py +45 -8
- Tools/tool_dir.json +16 -0
- Tools/tool_manager.py +66 -13
- Utils/executor_utils.py +0 -18
- cli.py +59 -0
- {pikoai-0.1.6.dist-info → pikoai-0.1.9.dist-info}/METADATA +1 -1
- pikoai-0.1.9.dist-info/RECORD +35 -0
- pikoai-0.1.6.dist-info/RECORD +0 -32
- {pikoai-0.1.6.dist-info → pikoai-0.1.9.dist-info}/WHEEL +0 -0
- {pikoai-0.1.6.dist-info → pikoai-0.1.9.dist-info}/entry_points.txt +0 -0
- {pikoai-0.1.6.dist-info → pikoai-0.1.9.dist-info}/licenses/LICENSE +0 -0
- {pikoai-0.1.6.dist-info → pikoai-0.1.9.dist-info}/top_level.txt +0 -0
Agents/Executor/executor.py
CHANGED
@@ -6,15 +6,17 @@ import sys
|
|
6
6
|
import time
|
7
7
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../')))
|
8
8
|
from Utils.ter_interface import TerminalInterface
|
9
|
-
from Utils.executor_utils import parse_tool_call
|
9
|
+
from Utils.executor_utils import parse_tool_call
|
10
10
|
from Agents.Executor.prompts import get_system_prompt, get_task_prompt # Import prompts
|
11
11
|
|
12
12
|
from typing import Optional
|
13
13
|
from mistralai.models.sdkerror import SDKError # This might be an issue if LiteLLM doesn't use SDKError
|
14
14
|
# LiteLLM maps exceptions to OpenAI exceptions.
|
15
15
|
# We'll keep it for now and see if errors arise during testing.
|
16
|
-
from Env import python_executor
|
17
|
-
from Env.shell import ShellExecutor #
|
16
|
+
# from Env import python_executor # Will be replaced by BaseEnv
|
17
|
+
# from Env.shell import ShellExecutor # Will be replaced by BaseEnv
|
18
|
+
from Src.Env.base_env import create_environment, BaseEnv # Added
|
19
|
+
from Env import python_executor # Keep for type hint in the old execute method if needed, or remove if execute is fully removed
|
18
20
|
from llm_interface.llm import LiteLLMInterface # Import LiteLLMInterface
|
19
21
|
|
20
22
|
from Tools import tool_manager
|
@@ -38,8 +40,8 @@ class executor:
|
|
38
40
|
self.max_iter = max_iter
|
39
41
|
self.rate_limiter = RateLimiter(wait_time=5.0, max_retries=3)
|
40
42
|
self.executor_prompt_init() # Update system_prompt
|
41
|
-
self.python_executor = python_executor.PythonExecutor() # Initialize PythonExecutor
|
42
|
-
self.shell_executor = ShellExecutor() # Initialize ShellExecutor
|
43
|
+
# self.python_executor = python_executor.PythonExecutor() # Initialize PythonExecutor
|
44
|
+
# self.shell_executor = ShellExecutor() # Initialize ShellExecutor
|
43
45
|
self.message = [
|
44
46
|
{"role": "system", "content": self.system_prompt},
|
45
47
|
{"role": "user", "content": self.task_prompt}
|
@@ -122,89 +124,29 @@ class executor:
|
|
122
124
|
# Check for tool calls
|
123
125
|
response = self.run_inference()
|
124
126
|
tool_call = parse_tool_call(response)
|
127
|
+
|
125
128
|
if tool_call:
|
126
|
-
|
129
|
+
tool_name = tool_call['tool_name']
|
130
|
+
tool_input = tool_call['input']
|
131
|
+
print(f"\nIdentified tool call: {tool_name} with input {tool_input}")
|
132
|
+
|
133
|
+
# Call the tool and append the result (no confirmation or special logic)
|
127
134
|
try:
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
self.message.append({"role": "user", "content":
|
135
|
+
tool_output_result = tool_manager.call_tool(tool_name, tool_input)
|
136
|
+
self.terminal.tool_output_log(tool_output_result, tool_name)
|
137
|
+
print(tool_output_result)
|
138
|
+
self.message.append({"role": "user", "content": tool_output_result})
|
132
139
|
except ValueError as e:
|
133
140
|
error_msg = str(e)
|
141
|
+
print(f"Tool Error: {error_msg}")
|
134
142
|
self.message.append({"role": "user", "content": f"Tool Error: {error_msg}"})
|
135
|
-
|
136
|
-
else: # Not a tool call,
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
user_confirmation = input("Do you want to execute the Python code?")
|
143
|
-
if user_confirmation.lower() == 'y':
|
144
|
-
exec_result = self.python_executor.execute(code)
|
145
|
-
if exec_result['output'] == "" and not exec_result['success']:
|
146
|
-
error_msg = (
|
147
|
-
f"Python execution failed.\n"
|
148
|
-
f"Error: {exec_result.get('error', 'Unknown error')}"
|
149
|
-
)
|
150
|
-
print(f"there was an error in the python code execution {exec_result.get('error', 'Unknown error')}")
|
151
|
-
self.message.append({"role": "user", "content": error_msg})
|
152
|
-
|
153
|
-
elif exec_result['output'] == "":
|
154
|
-
no_output_msg = (
|
155
|
-
"Python execution completed but no output was shown. "
|
156
|
-
"Please add print statements to show the results. This isn't a jupyter notebook environment. "
|
157
|
-
"For example: print(your_variable) or print('Your message')"
|
158
|
-
)
|
159
|
-
self.message.append({"role": "user", "content": no_output_msg})
|
160
|
-
|
161
|
-
#if there is an output (partial or full exeuction)
|
162
|
-
else:
|
163
|
-
# First, show the program output
|
164
|
-
if exec_result['output'].strip():
|
165
|
-
print(f"Program Output:\n{exec_result['output']}")
|
166
|
-
|
167
|
-
# Then handle success/failure cases
|
168
|
-
if exec_result['success']:
|
169
|
-
self.message.append({"role": "user", "content": f"Program Output:\n{exec_result['output']}"})
|
170
|
-
else:
|
171
|
-
self.message.append({"role": "user", "content": f"Program Output:\n{exec_result['output']}\n{exec_result.get('error', 'Unknown error')}"})
|
172
|
-
|
173
|
-
else:
|
174
|
-
self.message.append({"role":"user","content":"User chose not to execute the Python code."})
|
175
|
-
print("Python code execution skipped by the user.")
|
176
|
-
|
177
|
-
elif shell_command:
|
178
|
-
user_confirmation = input(f"Do you want to execute the shell command: '{shell_command}'?\n ")
|
179
|
-
if user_confirmation.lower() == 'y':
|
180
|
-
shell_result = self.shell_executor.execute(shell_command)
|
181
|
-
if shell_result['output'] == "" and not shell_result['success']:
|
182
|
-
error_msg = (
|
183
|
-
f"Shell command execution failed.\n"
|
184
|
-
f"Error: {shell_result.get('error', 'Unknown error')}"
|
185
|
-
)
|
186
|
-
print(f"there was an error in the shell command execution {shell_result.get('error', 'Unknown error')}")
|
187
|
-
self.message.append({"role": "user", "content": error_msg})
|
188
|
-
|
189
|
-
elif shell_result['output'] == "":
|
190
|
-
print("command executed")
|
191
|
-
self.message.append({"role": "user", "content": "command executed"})
|
192
|
-
|
193
|
-
#if there is an output (partial or full execution)
|
194
|
-
else:
|
195
|
-
# First, show the command output
|
196
|
-
if shell_result['output'].strip():
|
197
|
-
print(f"Command Output:\n{shell_result['output']}")
|
198
|
-
|
199
|
-
# Then handle success/failure cases
|
200
|
-
if shell_result['success']:
|
201
|
-
self.message.append({"role": "user", "content": f"Command Output:\n{shell_result['output']}"})
|
202
|
-
else:
|
203
|
-
self.message.append({"role": "user", "content": f"Command Output:\n{shell_result['output']}\n{shell_result.get('error', 'Unknown error')}"})
|
204
|
-
|
205
|
-
else:
|
206
|
-
self.message.append({"role":"user","content":"User chose not to execute the shell command."})
|
207
|
-
print("Shell command execution skipped by the user.")
|
143
|
+
|
144
|
+
else: # Not a tool call, could be a direct response or requires clarification
|
145
|
+
# This part handles responses that are not formatted as tool calls.
|
146
|
+
# It might be a final answer, a question, or just conversational text.
|
147
|
+
# The existing logic for TASK_DONE or asking for next step handles this.
|
148
|
+
# No specific code/shell parsing here anymore as they are tools.
|
149
|
+
pass # Explicitly pass if no tool call and no old code/shell logic.
|
208
150
|
|
209
151
|
# Check if task is done
|
210
152
|
if "TASK_DONE" in response:
|
@@ -218,24 +160,26 @@ class executor:
|
|
218
160
|
if not task_done:
|
219
161
|
print(f"Task could not be completed within {self.max_iter} iterations.")
|
220
162
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
163
|
+
# This method is superseded by the BaseEnv approach in run_task
|
164
|
+
# def execute(self, code: str, exec_env: python_executor.PythonExecutor):
|
165
|
+
# """Executes the given Python code using the provided execution environment."""
|
166
|
+
# result = exec_env.execute(code)
|
167
|
+
# return result
|
225
168
|
|
226
169
|
if __name__ == "__main__":
|
227
|
-
e1 = executor("")
|
228
|
-
user_prompt = input("Please enter your prompt: ")
|
229
|
-
e1.user_prompt = user_prompt
|
230
|
-
e1.executor_prompt_init() # Update system_prompt
|
231
|
-
e1.message = [
|
232
|
-
|
233
|
-
|
234
|
-
] # Reset message list properly
|
235
|
-
e1.run()
|
236
|
-
|
237
|
-
while True:
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
170
|
+
# e1 = executor("") # Commenting out example usage for now as it might need adjustment
|
171
|
+
# user_prompt = input("Please enter your prompt: ")
|
172
|
+
# e1.user_prompt = user_prompt
|
173
|
+
# e1.executor_prompt_init() # Update system_prompt
|
174
|
+
# e1.message = [
|
175
|
+
# {"role": "system", "content": e1.system_prompt},
|
176
|
+
# {"role": "user", "content": e1.task_prompt}
|
177
|
+
# ] # Reset message list properly
|
178
|
+
# e1.run()
|
179
|
+
|
180
|
+
# while True:
|
181
|
+
# user_prompt = input("Please enter your prompt: ")
|
182
|
+
# e1.message.append({"role": "user", "content": user_prompt})
|
183
|
+
# # e1.message.append({"role":"user","content":e1.system_prompt})
|
184
|
+
# e1.run()
|
185
|
+
pass # Placeholder if main execution is commented out
|
Agents/Executor/prompts.py
CHANGED
@@ -17,9 +17,7 @@ You have access to the following tools:
|
|
17
17
|
|
18
18
|
Your primary objective is to accomplish the user's goal by performing step-by-step actions. These actions can include:
|
19
19
|
1. Calling a tool
|
20
|
-
2.
|
21
|
-
3. Executing Shell commands
|
22
|
-
4. Providing a direct response
|
20
|
+
2. Providing a direct response
|
23
21
|
|
24
22
|
You must break down the user's goal into smaller steps and perform one action at a time. After each action, carefully evaluate the output to determine the next step.
|
25
23
|
|
@@ -33,14 +31,9 @@ You must break down the user's goal into smaller steps and perform one action at
|
|
33
31
|
}}
|
34
32
|
}}
|
35
33
|
<<END_TOOL_CALL>>
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
<<CODE>>
|
40
|
-
- **Shell Command Execution**: Execute shell commands when needed. Format:
|
41
|
-
<<SHELL_COMMAND>>
|
42
|
-
your_shell_command_here
|
43
|
-
<<END_SHELL_COMMAND>>
|
34
|
+
This includes executing Python code and shell commands:
|
35
|
+
`execute_python_code`: {{"code": "your_python_code_here"}}
|
36
|
+
`execute_shell_command`: {{"command": "your_shell_command_here"}}
|
44
37
|
- **Direct Response**: Provide a direct answer if the task doesn't require tools or code.
|
45
38
|
|
46
39
|
### Important Notes:
|
@@ -68,16 +61,7 @@ Following are the things that you must read carefully and remember:
|
|
68
61
|
}
|
69
62
|
}
|
70
63
|
<<END_TOOL_CALL>>
|
71
|
-
|
72
|
-
- For code execution, use:
|
73
|
-
<<CODE>>
|
74
|
-
your_python_code_here
|
75
|
-
<<CODE>>
|
76
|
-
|
77
|
-
- For shell command execution, use:
|
78
|
-
<<SHELL_COMMAND>>
|
79
|
-
your_shell_command_here
|
80
|
-
<<END_SHELL_COMMAND>>
|
64
|
+
Remember that executing Python code and shell commands is now done through specific tool calls (`execute_python_code` and `execute_shell_command`).
|
81
65
|
|
82
66
|
After each action, always evaluate the output to decide your next step. Only include 'TASK_DONE'
|
83
67
|
When the entire task is completed. Do not end the task immediately after a tool call or code execution without
|
Env/base_env.py
CHANGED
@@ -1,25 +1,30 @@
|
|
1
|
-
from Env.js_executor import JavaScriptExecutor #class import
|
2
|
-
from Env.python_executor import PythonExecutor #class import
|
3
|
-
|
4
1
|
# to perform funciton ask whether to execute code
|
5
2
|
|
6
3
|
|
7
4
|
class BaseEnv:
|
8
5
|
|
9
6
|
|
10
|
-
def __init__(self
|
11
|
-
|
7
|
+
def __init__(self):
|
8
|
+
pass
|
9
|
+
|
12
10
|
|
11
|
+
def execute(self, code_or_command: str):
|
12
|
+
raise NotImplementedError("This method should be overridden by subclasses")
|
13
13
|
|
14
|
-
def
|
14
|
+
def stop_execution(self):
|
15
15
|
raise NotImplementedError("This method should be overridden by subclasses")
|
16
16
|
|
17
17
|
|
18
18
|
def create_environment(language):
|
19
|
+
# Moved imports inside the function to avoid circular dependencies during testing
|
20
|
+
from Env.js_executor import JavaScriptExecutor #class import
|
21
|
+
from Env.python_executor import PythonExecutor #class import
|
22
|
+
from Env.shell import ShellExecutor #class import
|
23
|
+
|
19
24
|
if language == "python":
|
20
25
|
return PythonExecutor()
|
21
|
-
elif language == "
|
22
|
-
return
|
26
|
+
elif language == "shell":
|
27
|
+
return ShellExecutor()
|
23
28
|
else:
|
24
29
|
raise ValueError(f"Unsupported language: {language}")
|
25
30
|
|
Env/python_executor.py
CHANGED
@@ -21,9 +21,13 @@ import os
|
|
21
21
|
from typing import Dict
|
22
22
|
import textwrap
|
23
23
|
import sys
|
24
|
+
from Src.Env.base_env import BaseEnv
|
25
|
+
import time
|
24
26
|
|
25
|
-
class PythonExecutor:
|
27
|
+
class PythonExecutor(BaseEnv):
|
26
28
|
def __init__(self):
|
29
|
+
super().__init__()
|
30
|
+
self.process = None
|
27
31
|
self.forbidden_terms = [
|
28
32
|
'import os', 'import sys', 'import subprocess',
|
29
33
|
'open(', 'exec(', 'eval(',
|
@@ -34,52 +38,80 @@ class PythonExecutor:
|
|
34
38
|
code_lower = code.lower()
|
35
39
|
return not any(term.lower() in code_lower for term in self.forbidden_terms)
|
36
40
|
|
37
|
-
def execute(self,
|
41
|
+
def execute(self, code_or_command: str) -> Dict[str, str]:
|
38
42
|
"""Executes Python code in a separate process and returns the result"""
|
39
43
|
|
40
44
|
# Basic safety check
|
41
|
-
if not self.basic_code_check(
|
45
|
+
if not self.basic_code_check(code_or_command):
|
42
46
|
return {
|
43
47
|
'success': False,
|
44
48
|
'output': 'Error: Code contains potentially unsafe operations. You can try and use tools to achieve same functionality.',
|
45
49
|
'error': 'Security check failed'
|
46
50
|
}
|
47
51
|
|
48
|
-
#
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# Wrap the indented code to capture output
|
53
|
-
wrapped_code = f"""
|
52
|
+
# Properly indent the code to fit inside the try block
|
53
|
+
indented_code = textwrap.indent(code_or_command, ' ')
|
54
|
+
# Wrap the indented code to capture output
|
55
|
+
wrapped_code = f"""
|
54
56
|
try:
|
55
57
|
{indented_code}
|
56
58
|
except Exception as e:
|
57
59
|
print(f"Error: {{str(e)}}")
|
58
60
|
"""
|
59
|
-
|
60
|
-
temp_file = f.name
|
61
|
+
|
61
62
|
|
62
63
|
try:
|
63
64
|
# Execute the code in a subprocess
|
64
|
-
|
65
|
-
[sys.executable,
|
66
|
-
|
65
|
+
self.process = subprocess.Popen(
|
66
|
+
[sys.executable, "-u", "-c", wrapped_code],
|
67
|
+
stdout=subprocess.PIPE,
|
68
|
+
stderr=subprocess.PIPE,
|
67
69
|
text=True,
|
68
|
-
|
70
|
+
bufsize=1, # Line buffered
|
71
|
+
universal_newlines=True
|
69
72
|
)
|
70
|
-
|
73
|
+
|
74
|
+
stdout_data = []
|
75
|
+
stderr_data = []
|
76
|
+
start_time = time.time()
|
77
|
+
|
78
|
+
# First read all stdout
|
79
|
+
for line in self.process.stdout:
|
80
|
+
# Check for timeout
|
81
|
+
if time.time() - start_time > 30:
|
82
|
+
self.process.kill()
|
83
|
+
return {
|
84
|
+
'success': False,
|
85
|
+
'output': 'Execution timed out after 30 seconds',
|
86
|
+
'error': 'Timeout error'
|
87
|
+
}
|
88
|
+
|
89
|
+
stdout_data.append(line)
|
90
|
+
print(line, end='', flush=True) # Print in real-time
|
91
|
+
|
92
|
+
# Then read all stderr
|
93
|
+
for line in self.process.stderr:
|
94
|
+
# Check for timeout
|
95
|
+
if time.time() - start_time > 30:
|
96
|
+
self.process.kill()
|
97
|
+
return {
|
98
|
+
'success': False,
|
99
|
+
'output': 'Execution timed out after 30 seconds',
|
100
|
+
'error': 'Timeout error'
|
101
|
+
}
|
102
|
+
|
103
|
+
stderr_data.append(line)
|
104
|
+
print(line, end='', file=sys.stderr, flush=True) # Print in real-time
|
105
|
+
|
106
|
+
# Wait for process to complete
|
107
|
+
returncode = self.process.wait()
|
108
|
+
|
71
109
|
return {
|
72
|
-
'success':
|
73
|
-
'output':
|
74
|
-
'error':
|
110
|
+
'success': returncode == 0,
|
111
|
+
'output': ''.join(stdout_data) if returncode == 0 else ''.join(stderr_data),
|
112
|
+
'error': ''.join(stderr_data) if returncode != 0 else ''
|
75
113
|
}
|
76
114
|
|
77
|
-
except subprocess.TimeoutExpired:
|
78
|
-
return {
|
79
|
-
'success': False,
|
80
|
-
'output': 'Execution timed out after 30 seconds',
|
81
|
-
'error': 'Timeout error'
|
82
|
-
}
|
83
115
|
except Exception as e:
|
84
116
|
return {
|
85
117
|
'success': False,
|
@@ -87,10 +119,18 @@ except Exception as e:
|
|
87
119
|
'error': str(e)
|
88
120
|
}
|
89
121
|
finally:
|
90
|
-
|
122
|
+
self.process = None # Reset process
|
123
|
+
|
124
|
+
|
125
|
+
def stop_execution(self):
|
126
|
+
if self.process and hasattr(self.process, 'pid') and self.process.pid is not None:
|
91
127
|
try:
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
128
|
+
self.process.terminate()
|
129
|
+
print(f"Attempted to terminate Python process with PID: {self.process.pid}")
|
130
|
+
except Exception as e:
|
131
|
+
print(f"Error terminating Python process with PID {self.process.pid}: {e}")
|
132
|
+
finally:
|
133
|
+
self.process = None
|
134
|
+
else:
|
135
|
+
print("No active Python process to stop.")
|
96
136
|
|
Env/shell.py
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
import subprocess
|
2
|
+
import time
|
3
|
+
import sys
|
4
|
+
from Src.Env.base_env import BaseEnv
|
5
|
+
import re
|
2
6
|
|
3
|
-
class ShellExecutor:
|
4
|
-
def
|
7
|
+
class ShellExecutor(BaseEnv):
|
8
|
+
def __init__(self):
|
9
|
+
super().__init__()
|
10
|
+
self.process = None
|
11
|
+
|
12
|
+
def execute(self, code_or_command: str) -> dict:
|
5
13
|
"""
|
6
|
-
Executes a shell command and
|
14
|
+
Executes a shell command and streams its output in real-time.
|
15
|
+
Strictly prevents execution of harmful commands and access to sensitive directories.
|
7
16
|
|
8
17
|
Args:
|
9
|
-
|
18
|
+
code_or_command: The shell command to execute.
|
10
19
|
|
11
20
|
Returns:
|
12
21
|
A dictionary with the following keys:
|
@@ -15,24 +24,142 @@ class ShellExecutor:
|
|
15
24
|
- 'success': A boolean indicating whether the command executed successfully.
|
16
25
|
"""
|
17
26
|
try:
|
18
|
-
|
19
|
-
|
27
|
+
# Strict security check for harmful commands and sensitive directories
|
28
|
+
forbidden_patterns = [
|
29
|
+
r'rm\s+-rf\s+/?(\s|$)',
|
30
|
+
r'rm\s+-rf\s+--no-preserve-root',
|
31
|
+
r'rm\s+-rf\s+/\\?',
|
32
|
+
r'shutdown(\s|$)',
|
33
|
+
r'reboot(\s|$)',
|
34
|
+
r'halt(\s|$)',
|
35
|
+
r':(){:|:&};:', # fork bomb
|
36
|
+
r'chmod\s+777\s+/(\s|$)',
|
37
|
+
r'chown\s+root',
|
38
|
+
r'\bmkfs\b',
|
39
|
+
r'\bdd\b.*\bif=\/dev\/zero\b',
|
40
|
+
r'\bdd\b.*\bof=\/dev\/sda',
|
41
|
+
r'\bpoweroff\b',
|
42
|
+
r'\binit\s+0\b',
|
43
|
+
r'\bsudo\s+rm\s+-rf\s+/?',
|
44
|
+
r'\bsudo\s+shutdown',
|
45
|
+
r'\bsudo\s+reboot',
|
46
|
+
r'\bsudo\s+halt',
|
47
|
+
r'\bsudo\s+mkfs',
|
48
|
+
r'\bsudo\s+dd',
|
49
|
+
r'\bsudo\s+init\s+0',
|
50
|
+
r'\bsudo\s+poweroff',
|
51
|
+
r'\bsudo\s+chmod\s+777\s+/',
|
52
|
+
r'\bsudo\s+chown\s+root',
|
53
|
+
r'\bdel\b.*\/s.*\/q.*\/f.*C:\\', # Windows
|
54
|
+
r'format\s+C:',
|
55
|
+
r'rd\s+/?s\s+/?q\s+C:\\',
|
56
|
+
r'\bshutdown\b.*\/s',
|
57
|
+
r'\bshutdown\b.*\/r',
|
58
|
+
r'\bshutdown\b.*\/f',
|
59
|
+
r'\bshutdown\b.*\/p',
|
60
|
+
r'\bshutdown\b.*\/t',
|
61
|
+
r'\bshutdown\b.*\/a',
|
62
|
+
r'\bnet\s+user\s+.*\s+/delete',
|
63
|
+
r'\bnet\s+user\s+administrator\s+/active:no',
|
64
|
+
r'\bnet\s+user\s+administrator\s+/active:yes',
|
65
|
+
r'\bnet\s+localgroup\s+administrators\s+.*\s+/delete',
|
66
|
+
r'\bnet\s+localgroup\s+administrators\s+.*\s+/add',
|
67
|
+
]
|
68
|
+
sensitive_dirs = [
|
69
|
+
'/', '/etc', '/bin', '/usr', '/var', '/root', '/boot', '/dev', '/proc', '/sys', '/lib', '/lib64',
|
70
|
+
'C:\\', 'C:/', 'C:\\Windows', 'C:/Windows', 'C:\\System32', 'C:/System32',
|
71
|
+
'D:\\', 'D:/', 'E:\\', 'E:/'
|
72
|
+
]
|
73
|
+
# Check for forbidden patterns
|
74
|
+
for pattern in forbidden_patterns:
|
75
|
+
if re.search(pattern, code_or_command, re.IGNORECASE):
|
76
|
+
return {
|
77
|
+
'success': False,
|
78
|
+
'output': 'Blocked potentially harmful command.',
|
79
|
+
'error': f'Command matches forbidden pattern: {pattern}'
|
80
|
+
}
|
81
|
+
# Check for sensitive directory access
|
82
|
+
for sensitive_dir in sensitive_dirs:
|
83
|
+
# Only block if the command is trying to directly access or operate on the sensitive dir
|
84
|
+
if re.search(rf'\b{sensitive_dir}\b', code_or_command, re.IGNORECASE):
|
85
|
+
return {
|
86
|
+
'success': False,
|
87
|
+
'output': f'Blocked access to sensitive directory: {sensitive_dir}',
|
88
|
+
'error': f'Attempted access to sensitive directory: {sensitive_dir}'
|
89
|
+
}
|
90
|
+
|
91
|
+
# Execute the command in a subprocess
|
92
|
+
self.process = subprocess.Popen(
|
93
|
+
code_or_command,
|
20
94
|
shell=True,
|
21
|
-
|
95
|
+
stdout=subprocess.PIPE,
|
96
|
+
stderr=subprocess.PIPE,
|
22
97
|
text=True,
|
23
|
-
|
98
|
+
bufsize=1, # Line buffered
|
99
|
+
universal_newlines=True
|
24
100
|
)
|
101
|
+
|
102
|
+
stdout_data = []
|
103
|
+
stderr_data = []
|
104
|
+
start_time = time.time()
|
105
|
+
|
106
|
+
# First read all stdout
|
107
|
+
for line in self.process.stdout:
|
108
|
+
# Check for timeout
|
109
|
+
if time.time() - start_time > 30:
|
110
|
+
self.process.kill()
|
111
|
+
return {
|
112
|
+
'success': False,
|
113
|
+
'output': 'Execution timed out after 30 seconds',
|
114
|
+
'error': 'Timeout error'
|
115
|
+
}
|
116
|
+
|
117
|
+
stdout_data.append(line)
|
118
|
+
print(line, end='', flush=True) # Print in real-time
|
119
|
+
|
120
|
+
# Then read all stderr
|
121
|
+
for line in self.process.stderr:
|
122
|
+
# Check for timeout
|
123
|
+
if time.time() - start_time > 30:
|
124
|
+
self.process.kill()
|
125
|
+
return {
|
126
|
+
'success': False,
|
127
|
+
'output': 'Execution timed out after 30 seconds',
|
128
|
+
'error': 'Timeout error'
|
129
|
+
}
|
130
|
+
|
131
|
+
stderr_data.append(line)
|
132
|
+
print(line, end='', file=sys.stderr, flush=True) # Print in real-time
|
133
|
+
|
134
|
+
# Wait for process to complete
|
135
|
+
returncode = self.process.wait()
|
136
|
+
|
25
137
|
return {
|
26
|
-
|
27
|
-
|
28
|
-
|
138
|
+
'success': returncode == 0,
|
139
|
+
'output': ''.join(stdout_data) if returncode == 0 else ''.join(stderr_data),
|
140
|
+
'error': ''.join(stderr_data) if returncode != 0 else ''
|
29
141
|
}
|
142
|
+
|
30
143
|
except Exception as e:
|
31
144
|
return {
|
32
|
-
|
33
|
-
|
34
|
-
|
145
|
+
'success': False,
|
146
|
+
'output': f'Error: {str(e)}',
|
147
|
+
'error': str(e)
|
35
148
|
}
|
149
|
+
finally:
|
150
|
+
self.process = None # Reset process
|
151
|
+
|
152
|
+
def stop_execution(self):
|
153
|
+
if self.process and hasattr(self.process, 'pid') and self.process.pid is not None:
|
154
|
+
try:
|
155
|
+
self.process.terminate()
|
156
|
+
print(f"Attempted to terminate shell process with PID: {self.process.pid}")
|
157
|
+
except Exception as e:
|
158
|
+
print(f"Error terminating shell process with PID {self.process.pid}: {e}")
|
159
|
+
finally:
|
160
|
+
self.process = None
|
161
|
+
else:
|
162
|
+
print("No active shell process to stop.")
|
36
163
|
|
37
164
|
if __name__ == '__main__':
|
38
165
|
# Example usage (optional, for testing)
|
Env/tests/__init__.py
ADDED
File without changes
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import unittest
|
2
|
+
from Src.Env.python_executor import PythonExecutor
|
3
|
+
import time
|
4
|
+
import threading
|
5
|
+
|
6
|
+
class TestPythonExecutor(unittest.TestCase):
|
7
|
+
|
8
|
+
def test_execute_simple_script(self):
|
9
|
+
executor = PythonExecutor()
|
10
|
+
result = executor.execute("print('hello python')")
|
11
|
+
self.assertTrue(result["success"])
|
12
|
+
self.assertEqual(result["output"].strip(), "hello python")
|
13
|
+
self.assertEqual(result["error"], "")
|
14
|
+
|
15
|
+
def test_execute_script_with_error(self):
|
16
|
+
executor = PythonExecutor()
|
17
|
+
result = executor.execute("1/0")
|
18
|
+
self.assertFalse(result["success"])
|
19
|
+
self.assertTrue("ZeroDivisionError" in result["output"]) # Error message goes to stdout for python_executor
|
20
|
+
self.assertEqual(result["error"], "") # stderr should be empty as Popen merges stderr to stdout in this case
|
21
|
+
|
22
|
+
def test_stop_execution_long_script(self):
|
23
|
+
executor = PythonExecutor()
|
24
|
+
long_script = "import time; time.sleep(5); print('should not print')"
|
25
|
+
|
26
|
+
execute_result = {}
|
27
|
+
def target():
|
28
|
+
res = executor.execute(long_script)
|
29
|
+
execute_result.update(res)
|
30
|
+
|
31
|
+
thread = threading.Thread(target=target)
|
32
|
+
thread.start()
|
33
|
+
|
34
|
+
time.sleep(1) # Give the script time to start
|
35
|
+
executor.stop_execution()
|
36
|
+
|
37
|
+
thread.join(timeout=2) # Wait for the thread to finish (should be quick after stop)
|
38
|
+
|
39
|
+
self.assertFalse(execute_result.get("success", True), "Script execution should have failed or been stopped.")
|
40
|
+
# Depending on timing, the process might be killed before it produces output,
|
41
|
+
# or it might produce a timeout error, or a specific error from being terminated.
|
42
|
+
# We check if the output indicates it didn't complete normally.
|
43
|
+
output = execute_result.get("output", "")
|
44
|
+
error = execute_result.get("error", "")
|
45
|
+
|
46
|
+
# Check if 'should not print' is NOT in the output
|
47
|
+
self.assertNotIn("should not print", output, "Script should have been terminated before completion.")
|
48
|
+
|
49
|
+
# Check for signs of termination or timeout
|
50
|
+
# This part is a bit tricky as the exact message can vary.
|
51
|
+
# If basic_code_check fails, output is "Error: Code contains potentially unsafe operations..."
|
52
|
+
# If timeout in communicate(), output is "Execution timed out..."
|
53
|
+
# If process is terminated, output might be empty or contain partial error.
|
54
|
+
# For now, we'll accept that if "should not print" is not there, it's a good sign.
|
55
|
+
# A more robust check might involve looking for specific error messages related to termination if available.
|
56
|
+
|
57
|
+
# A simple check that process is no longer listed in executor
|
58
|
+
self.assertIsNone(executor.process, "Executor process should be None after stopping.")
|
59
|
+
|
60
|
+
|
61
|
+
def test_stop_execution_no_script(self):
|
62
|
+
executor = PythonExecutor()
|
63
|
+
try:
|
64
|
+
executor.stop_execution()
|
65
|
+
except Exception as e:
|
66
|
+
self.fail(f"stop_execution with no script raised an exception: {e}")
|
67
|
+
self.assertIsNone(executor.process, "Executor process should be None.")
|
68
|
+
|
69
|
+
if __name__ == '__main__':
|
70
|
+
unittest.main()
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import unittest
|
2
|
+
from Src.Env.shell import ShellExecutor
|
3
|
+
|
4
|
+
class TestShellExecutor(unittest.TestCase):
|
5
|
+
|
6
|
+
def test_execute_success(self):
|
7
|
+
executor = ShellExecutor()
|
8
|
+
result = executor.execute("echo hello")
|
9
|
+
self.assertTrue(result["success"])
|
10
|
+
self.assertEqual(result["output"].strip(), "hello")
|
11
|
+
self.assertEqual(result["error"], "")
|
12
|
+
|
13
|
+
def test_execute_error(self):
|
14
|
+
executor = ShellExecutor()
|
15
|
+
result = executor.execute("ls non_existent_directory_for_test")
|
16
|
+
self.assertFalse(result["success"])
|
17
|
+
self.assertEqual(result["output"], "")
|
18
|
+
self.assertTrue("non_existent_directory_for_test" in result["error"])
|
19
|
+
|
20
|
+
def test_stop_execution(self):
|
21
|
+
executor = ShellExecutor()
|
22
|
+
# This test mainly ensures that calling stop_execution doesn't raise an error
|
23
|
+
try:
|
24
|
+
executor.stop_execution()
|
25
|
+
except Exception as e:
|
26
|
+
self.fail(f"stop_execution raised an exception: {e}")
|
27
|
+
|
28
|
+
if __name__ == '__main__':
|
29
|
+
unittest.main()
|
OpenCopilot.py
CHANGED
@@ -6,9 +6,40 @@ from prompt_toolkit import PromptSession, HTML
|
|
6
6
|
from prompt_toolkit.completion import Completer, Completion
|
7
7
|
from prompt_toolkit.shortcuts import print_formatted_text
|
8
8
|
from prompt_toolkit.formatted_text import FormattedText
|
9
|
+
from prompt_toolkit.key_binding import KeyBindings
|
10
|
+
from prompt_toolkit.application.current import get_app
|
9
11
|
|
12
|
+
from Tools.file_task import file_reader
|
10
13
|
from Agents.Executor.executor import executor
|
11
14
|
|
15
|
+
# Custom key bindings for handling Enter key
|
16
|
+
kb = KeyBindings()
|
17
|
+
|
18
|
+
@kb.add('enter')
|
19
|
+
def handle_enter(event):
|
20
|
+
"""Handle Enter key press based on completion state"""
|
21
|
+
buff = event.current_buffer
|
22
|
+
app = get_app()
|
23
|
+
|
24
|
+
# If completion menu is open, select the current completion
|
25
|
+
if app.current_buffer.complete_state:
|
26
|
+
# Get the current completion
|
27
|
+
current_completion = app.current_buffer.complete_state.current_completion
|
28
|
+
if current_completion:
|
29
|
+
# Get the text before cursor
|
30
|
+
text_before_cursor = buff.text[:buff.cursor_position]
|
31
|
+
# Find the last @ symbol
|
32
|
+
last_at_pos = text_before_cursor.rindex('@')
|
33
|
+
# Delete text from @ to cursor position
|
34
|
+
buff.delete_before_cursor(count=buff.cursor_position - last_at_pos - 1)
|
35
|
+
# Insert the completion
|
36
|
+
buff.insert_text(current_completion.text)
|
37
|
+
# Close the completion menu
|
38
|
+
buff.complete_state = None
|
39
|
+
else:
|
40
|
+
# If no completion menu, submit the command
|
41
|
+
buff.validate_and_handle()
|
42
|
+
|
12
43
|
class FilePathCompleter(Completer):
|
13
44
|
def get_completions(self, document, complete_event):
|
14
45
|
text_before_cursor = document.text_before_cursor
|
@@ -74,7 +105,12 @@ class FilePathCompleter(Completer):
|
|
74
105
|
class OpenCopilot:
|
75
106
|
def __init__(self):
|
76
107
|
self.e1 = None # Initialize as None, will be set in run()
|
77
|
-
|
108
|
+
# Initialize session with custom key bindings
|
109
|
+
self.session = PromptSession(
|
110
|
+
completer=FilePathCompleter(),
|
111
|
+
key_bindings=kb, # Add custom key bindings
|
112
|
+
complete_while_typing=True # Enable completion while typing
|
113
|
+
)
|
78
114
|
|
79
115
|
def extract_files_and_process_prompt(self, user_input):
|
80
116
|
"""Extract file paths from @ commands and process the prompt."""
|
@@ -93,10 +129,11 @@ class OpenCopilot:
|
|
93
129
|
|
94
130
|
if os.path.exists(expanded_path):
|
95
131
|
if os.path.isfile(expanded_path):
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
132
|
+
# Call file_reader to get file content
|
133
|
+
file_read_result = file_reader(file_path=expanded_path)
|
134
|
+
|
135
|
+
if file_read_result["success"]:
|
136
|
+
content = file_read_result["output"]
|
100
137
|
# Add file content with clear formatting
|
101
138
|
file_contents.append(f"=== Content of file: {expanded_path} ===\n{content}\n=== End of file: {expanded_path} ===\n")
|
102
139
|
# Remove the @file pattern from the processed prompt
|
@@ -105,10 +142,10 @@ class OpenCopilot:
|
|
105
142
|
print_formatted_text(FormattedText([
|
106
143
|
('class:success', f"✓ Loaded file: {expanded_path}")
|
107
144
|
]))
|
108
|
-
|
109
|
-
|
145
|
+
else:
|
146
|
+
error_message = file_read_result["output"]
|
110
147
|
print_formatted_text(FormattedText([
|
111
|
-
('class:error', f"✗ Error reading file {expanded_path}: {
|
148
|
+
('class:error', f"✗ Error reading file {expanded_path}: {error_message}")
|
112
149
|
]))
|
113
150
|
else:
|
114
151
|
# For directories, just append the path to the processed prompt
|
Tools/tool_dir.json
CHANGED
@@ -70,5 +70,21 @@
|
|
70
70
|
"prompt": "The message to display to the user",
|
71
71
|
"input_type": "Type of input to validate (text, number, boolean, optional, defaults to text)"
|
72
72
|
}
|
73
|
+
},
|
74
|
+
{
|
75
|
+
"name": "execute_python_code",
|
76
|
+
"summary": "Executes a given snippet of Python code. The code should be self-contained. Input should be a JSON object with a 'code' key containing the Python code string.",
|
77
|
+
"arguments": {
|
78
|
+
"code": "The Python code to execute."
|
79
|
+
},
|
80
|
+
"input_schema": {"type": "object", "properties": {"code": {"type": "string"}}, "required": ["code"]}
|
81
|
+
},
|
82
|
+
{
|
83
|
+
"name": "execute_shell_command",
|
84
|
+
"summary": "Executes a given shell command. Input should be a JSON object with a 'command' key containing the shell command string.",
|
85
|
+
"arguments": {
|
86
|
+
"command": "The shell command to execute."
|
87
|
+
},
|
88
|
+
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}
|
73
89
|
}
|
74
90
|
]
|
Tools/tool_manager.py
CHANGED
@@ -7,26 +7,64 @@ from Tools.web_search import web_search
|
|
7
7
|
from Tools.file_task import file_reader, file_maker, file_writer, directory_maker
|
8
8
|
from Tools.system_details import get_os_details, get_datetime, get_memory_usage, get_cpu_info
|
9
9
|
from Tools.userinp import get_user_input
|
10
|
+
from Src.Env.python_executor import PythonExecutor
|
11
|
+
from Src.Env.shell import ShellExecutor
|
10
12
|
|
11
13
|
#need to transform it into map of dictionary
|
12
14
|
#name : [function : xyz,description : blah bah]
|
13
15
|
|
14
|
-
tools_function_map = {
|
15
|
-
"web_loader": load_data,
|
16
|
-
"web_search": web_search,
|
17
|
-
"file_maker": file_maker,
|
18
|
-
"file_reader":file_reader,
|
19
|
-
"directory_maker":directory_maker,
|
20
|
-
"file_writer":file_writer,
|
21
|
-
"get_os_details": get_os_details,
|
22
|
-
"get_datetime": get_datetime,
|
23
|
-
"get_memory_usage": get_memory_usage,
|
24
|
-
"get_cpu_info": get_cpu_info,
|
25
|
-
"get_user_input": get_user_input
|
26
|
-
}
|
27
16
|
|
28
17
|
|
29
18
|
|
19
|
+
def execute_python_code_tool(code: str) -> str:
|
20
|
+
"""
|
21
|
+
Prompts for confirmation, then executes the given Python code and returns a formatted result string.
|
22
|
+
"""
|
23
|
+
user_confirmation = input(f"Do you want to execute this Python code snippet?\n```python\n{code}\n```\n(y/n): ")
|
24
|
+
if user_confirmation.lower() != 'y':
|
25
|
+
print("Python code execution skipped by the user.")
|
26
|
+
return "User chose not to execute the Python code."
|
27
|
+
executor = PythonExecutor()
|
28
|
+
result = executor.execute(code)
|
29
|
+
if result['output'] == "" and not result['success']:
|
30
|
+
error_msg = (
|
31
|
+
f"Python execution failed.\n"
|
32
|
+
f"Error: {result.get('error', 'Unknown error')}"
|
33
|
+
)
|
34
|
+
return error_msg
|
35
|
+
elif result['output'] == "":
|
36
|
+
no_output_msg = (
|
37
|
+
"Python execution completed but no output was produced. "
|
38
|
+
"Ensure your code includes print() statements to show results."
|
39
|
+
)
|
40
|
+
return no_output_msg
|
41
|
+
else:
|
42
|
+
if result['success']:
|
43
|
+
return f"Program Output:\n{result['output']}"
|
44
|
+
else:
|
45
|
+
return f"Program Output:\n{result['output']}\nError: {result.get('error', 'Unknown error')}"
|
46
|
+
|
47
|
+
def execute_shell_command_tool(command: str) -> str:
|
48
|
+
"""
|
49
|
+
Prompts for confirmation, then executes the given shell command and returns a formatted result string.
|
50
|
+
"""
|
51
|
+
user_confirmation = input(f"Do you want to execute the shell command: '{command}'? (y/n): ")
|
52
|
+
if user_confirmation.lower() != 'y':
|
53
|
+
print("Shell command execution skipped by the user.")
|
54
|
+
return "User chose not to execute the shell command."
|
55
|
+
executor = ShellExecutor()
|
56
|
+
result = executor.execute(command)
|
57
|
+
if result['output'] == "":
|
58
|
+
if result['success']:
|
59
|
+
return "Shell command executed successfully with no output."
|
60
|
+
else:
|
61
|
+
return f"Shell command executed with no output, but an error occurred: {result.get('error', 'Unknown error')}"
|
62
|
+
else:
|
63
|
+
if result['success']:
|
64
|
+
return f"Command Output:\n{result['output']}"
|
65
|
+
else:
|
66
|
+
return f"Command Output:\n{result['output']}\nError: {result.get('error', 'Unknown error')}"
|
67
|
+
|
30
68
|
def call_tool(tool_name, tool_input):
|
31
69
|
"""
|
32
70
|
Calls the appropriate tool function with the given input.
|
@@ -41,6 +79,21 @@ def call_tool(tool_name, tool_input):
|
|
41
79
|
else:
|
42
80
|
raise ValueError(f"Tool '{tool_name}' not found. Check the tools available in the tool directory")
|
43
81
|
|
82
|
+
tools_function_map = {
|
83
|
+
"web_loader": load_data,
|
84
|
+
"web_search": web_search,
|
85
|
+
"file_maker": file_maker,
|
86
|
+
"file_reader":file_reader,
|
87
|
+
"directory_maker":directory_maker,
|
88
|
+
"file_writer":file_writer,
|
89
|
+
"get_os_details": get_os_details,
|
90
|
+
"get_datetime": get_datetime,
|
91
|
+
"get_memory_usage": get_memory_usage,
|
92
|
+
"get_cpu_info": get_cpu_info,
|
93
|
+
"get_user_input": get_user_input,
|
94
|
+
"execute_python_code": execute_python_code_tool,
|
95
|
+
"execute_shell_command": execute_shell_command_tool,
|
96
|
+
}
|
44
97
|
|
45
98
|
# print(call_tool("web_loader","https://www.toastmasters.org"))
|
46
99
|
# print(call_tool("web_search","manus ai"))
|
Utils/executor_utils.py
CHANGED
@@ -13,21 +13,3 @@ def parse_tool_call(response: str) -> Optional[dict]:
|
|
13
13
|
except json.JSONDecodeError:
|
14
14
|
return None
|
15
15
|
return None
|
16
|
-
|
17
|
-
def parse_code(response: str) -> Optional[str]:
|
18
|
-
"""
|
19
|
-
Parses code from the response.
|
20
|
-
"""
|
21
|
-
if "<<CODE>>" in response and "<<CODE>>" in response: # There was a typo in the original file, it checked for <<CODE>> twice
|
22
|
-
code = response.split("<<CODE>>")[1].split("<<CODE>>")[0].strip()
|
23
|
-
return code
|
24
|
-
return None
|
25
|
-
|
26
|
-
def parse_shell_command(response: str) -> Optional[str]:
|
27
|
-
"""
|
28
|
-
Parses a shell command from the response.
|
29
|
-
"""
|
30
|
-
if "<<SHELL_COMMAND>>" in response and "<<END_SHELL_COMMAND>>" in response:
|
31
|
-
shell_command = response.split("<<SHELL_COMMAND>>")[1].split("<<END_SHELL_COMMAND>>")[0].strip()
|
32
|
-
return shell_command
|
33
|
-
return None
|
cli.py
CHANGED
@@ -296,5 +296,64 @@ def list_models():
|
|
296
296
|
for model_name_full in model_list: # model_name_full is "openai/gpt-4o", etc.
|
297
297
|
click.echo(f" - {model_name_full}")
|
298
298
|
|
299
|
+
@cli.command('set-api-key')
|
300
|
+
@click.option('--provider', '-p', type=click.Choice(list(AVAILABLE_MODELS.keys())),
|
301
|
+
help='The LLM provider to set API key for')
|
302
|
+
@click.option('--key', '-k', help='The API key to set (if not provided, will prompt for it)')
|
303
|
+
def set_api_key(provider, key):
|
304
|
+
"""Set or update API key for a specific LLM provider"""
|
305
|
+
if not provider:
|
306
|
+
# If no provider specified, ask user to choose
|
307
|
+
questions = [
|
308
|
+
inquirer.List('provider_key',
|
309
|
+
message="Select LLM Provider to update API key",
|
310
|
+
choices=list(AVAILABLE_MODELS.keys())
|
311
|
+
)
|
312
|
+
]
|
313
|
+
provider = inquirer.prompt(questions)['provider_key']
|
314
|
+
|
315
|
+
# Get the environment variable name for this provider
|
316
|
+
env_var = API_KEYS.get(provider)
|
317
|
+
if not env_var:
|
318
|
+
raise ValueError(f"Unknown provider: {provider}")
|
319
|
+
|
320
|
+
# Get the API key (either from command line or prompt)
|
321
|
+
if not key:
|
322
|
+
questions = [
|
323
|
+
inquirer.Text('api_key',
|
324
|
+
message=f"Enter your {provider.upper()} API key",
|
325
|
+
validate=lambda _, x: len(x.strip()) > 0
|
326
|
+
)
|
327
|
+
]
|
328
|
+
key = inquirer.prompt(questions)['api_key']
|
329
|
+
|
330
|
+
# Get the path to .env file
|
331
|
+
env_path = os.path.join(os.path.dirname(__file__), '../.env')
|
332
|
+
|
333
|
+
# Read existing .env file if it exists
|
334
|
+
lines = []
|
335
|
+
if os.path.exists(env_path):
|
336
|
+
with open(env_path, 'r') as f:
|
337
|
+
lines = f.readlines()
|
338
|
+
|
339
|
+
# Update or add the API key
|
340
|
+
key_line = f"{env_var}={key}\n"
|
341
|
+
key_exists = False
|
342
|
+
|
343
|
+
for i, line in enumerate(lines):
|
344
|
+
if line.strip().startswith(f"{env_var}=") or line.strip().startswith(f"#{env_var}="):
|
345
|
+
lines[i] = key_line
|
346
|
+
key_exists = True
|
347
|
+
break
|
348
|
+
|
349
|
+
if not key_exists:
|
350
|
+
lines.append(key_line)
|
351
|
+
|
352
|
+
# Write back to .env file
|
353
|
+
with open(env_path, 'w') as f:
|
354
|
+
f.writelines(lines)
|
355
|
+
|
356
|
+
click.echo(f"API key for {provider.upper()} has been updated successfully in {env_path}")
|
357
|
+
|
299
358
|
if __name__ == '__main__':
|
300
359
|
cli()
|
@@ -0,0 +1,35 @@
|
|
1
|
+
OpenCopilot.py,sha256=vBAuU6MNhYdnyIIrDWOBSlxa0iyjADJLq5eCxUhlffw,12240
|
2
|
+
cli.py,sha256=hY6KUxvKvJOFThZT--r6m2nzOEMIOVtiVptewsi9Z9w,13868
|
3
|
+
Agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
Agents/Executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
Agents/Executor/executor.py,sha256=lbtJejQBt7O4kBf-_JG1QM9QG7RHCO8VVqlLn00S7FE,8812
|
6
|
+
Agents/Executor/prompts.py,sha256=bcVxBBCEda7JZtnA1KoMM8KayKAqFeQCP4lY6EbodzQ,3607
|
7
|
+
Env/__init__.py,sha256=KLe7UcNV5L395SxhMwbYGyu7KPrSNaoV_9QJo3mLop0,196
|
8
|
+
Env/base_env.py,sha256=K4PoWwPXn3pKeu7_-JOlUuyNbyYQ9itMhQybFOm-3K4,1563
|
9
|
+
Env/base_executor.py,sha256=awTwJ44CKWV4JO2KUHfHDX0p1Ujw55hlaL5oNYTEW9M,893
|
10
|
+
Env/env.py,sha256=I3lOoyBJR5QNq3tWk_HVH6ncRx1vnilRYmj7b7h4jic,114
|
11
|
+
Env/js_executor.py,sha256=tEAg5ho8Pa8LzxUbS1Idau8XuJWZZqPiNlvFPDwGkgc,2690
|
12
|
+
Env/python_executor.py,sha256=i1SelJ2GdQpGVjtAVQmp__0bA95OurnU4DUyu_Yfb9g,4818
|
13
|
+
Env/shell.py,sha256=7jbSMPh-1Cx6xCzvS78MQbneYfBSNlItl-EHjDR1Zw4,7294
|
14
|
+
Env/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
Env/tests/test_python_executor.py,sha256=5kfs0cOf-RgWTOers_1wB0yvYSF-HrHPsraJ-PxgR50,3156
|
16
|
+
Env/tests/test_shell_executor.py,sha256=-RcCdSUMoRAXHISIh0IR5MCutco80fX2S3sQBcinc_Q,1034
|
17
|
+
Tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
+
Tools/file_task.py,sha256=VUhWq_G-SWvGahQo8PG7TOpElHUW3BGLUabrTdJS89o,12151
|
19
|
+
Tools/system_details.py,sha256=7-mTm3CG4NoatHcvcosalEgEcpWlNsCsZ7kuS3y_EmY,2262
|
20
|
+
Tools/tool_dir.json,sha256=pEqI-1rKud-VEWHYLF2Pbv6xwQZWy62HUF_1hjcdhCY,3128
|
21
|
+
Tools/tool_manager.py,sha256=Q42jnN9eoBm98cgyEpAnINPZYKegdKO-T6zQY2Ns2jE,4129
|
22
|
+
Tools/userinp.py,sha256=vUhEj3y1W1_ZFHqo2xQwvqDyeOg3VsisSKTI0EurUH8,1205
|
23
|
+
Tools/web_loader.py,sha256=PyZk2g7WngZT0tCLs9Danx20dYspnaZwy4rlVE9Sx_4,5054
|
24
|
+
Tools/web_search.py,sha256=4EGq1VZqfDgG-_yXTd4_Ha1iEUcR-szdlgRV7oFPru4,1259
|
25
|
+
Utils/__init__.py,sha256=oukU0ufroPRd8_N8d2xiFes9CTxSaw4NA6p2nS1kkSg,16
|
26
|
+
Utils/executor_utils.py,sha256=WwK3TKgw_hG_crg7ijRaqfidYnnNXYbbs37vKZRYK-0,491
|
27
|
+
Utils/ter_interface.py,sha256=Zay9mwyAyKYTNQAKOWXHAa3upo9TWprSf26STiHXk0g,6255
|
28
|
+
llm_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
|
+
llm_interface/llm.py,sha256=tI_KDOW14QLWowA7bB3GPe2qjlk0sjS5fBavs9XD1fo,5185
|
30
|
+
pikoai-0.1.9.dist-info/licenses/LICENSE,sha256=cELUVOboOAderKFp8bdtcM5VyJi61YH1oDbRhOuoQZw,1067
|
31
|
+
pikoai-0.1.9.dist-info/METADATA,sha256=4S0C2M5G7pjDx37Ydv40ks8jTL94-r1Nah00AJcOj6Q,2961
|
32
|
+
pikoai-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
33
|
+
pikoai-0.1.9.dist-info/entry_points.txt,sha256=xjZnheDymNDnQ0o84R0jZKEITrhNbzQWN-AhqfA_d6s,50
|
34
|
+
pikoai-0.1.9.dist-info/top_level.txt,sha256=hWzBNE7UQsuNcENIOksGcJED08k3ZGRRn2X5jnStICU,53
|
35
|
+
pikoai-0.1.9.dist-info/RECORD,,
|
pikoai-0.1.6.dist-info/RECORD
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
OpenCopilot.py,sha256=6210OONj0yV-LPzkCdZ1RUiHC5NZvLsD-y93Y1zurX0,10687
|
2
|
-
cli.py,sha256=o2V_DETYXEiYm6617Xg8rDcAgb0ofmojPvdlOpNHwMY,11809
|
3
|
-
Agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
Agents/Executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
Agents/Executor/executor.py,sha256=zaFO4IQNE1PscvGPV8zoRsQAJAYVTz2JsD_zUBLyYwg,12360
|
6
|
-
Agents/Executor/prompts.py,sha256=wLS3lPAYWjeKF02LzJ8vP5bZ2VQrMJUd4A7rBfl6qSQ,3846
|
7
|
-
Env/__init__.py,sha256=KLe7UcNV5L395SxhMwbYGyu7KPrSNaoV_9QJo3mLop0,196
|
8
|
-
Env/base_env.py,sha256=ORM6U5qwj7cTuSHFtSmCSsE0cl6pZ28D97CEyyFnucI,1323
|
9
|
-
Env/base_executor.py,sha256=awTwJ44CKWV4JO2KUHfHDX0p1Ujw55hlaL5oNYTEW9M,893
|
10
|
-
Env/env.py,sha256=I3lOoyBJR5QNq3tWk_HVH6ncRx1vnilRYmj7b7h4jic,114
|
11
|
-
Env/js_executor.py,sha256=tEAg5ho8Pa8LzxUbS1Idau8XuJWZZqPiNlvFPDwGkgc,2690
|
12
|
-
Env/python_executor.py,sha256=KiQxyxLgdEph1iRBLteqYy6KJGE8ag_jxiYJ_-BEEB4,3221
|
13
|
-
Env/shell.py,sha256=gr6czmeuSWtB3xSA9TZN7wnK2BENOuA9zjNttwbxztU,1877
|
14
|
-
Tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
Tools/file_task.py,sha256=VUhWq_G-SWvGahQo8PG7TOpElHUW3BGLUabrTdJS89o,12151
|
16
|
-
Tools/system_details.py,sha256=7-mTm3CG4NoatHcvcosalEgEcpWlNsCsZ7kuS3y_EmY,2262
|
17
|
-
Tools/tool_dir.json,sha256=1zF0Z8ATZn5GT5lGq0cGRlBQoh0F655RP_htkZwnJHI,2302
|
18
|
-
Tools/tool_manager.py,sha256=0i3bd_VxhbpWKLzyfSeYyv_33Z6HmvQDBUxPUxjLYlU,1736
|
19
|
-
Tools/userinp.py,sha256=vUhEj3y1W1_ZFHqo2xQwvqDyeOg3VsisSKTI0EurUH8,1205
|
20
|
-
Tools/web_loader.py,sha256=PyZk2g7WngZT0tCLs9Danx20dYspnaZwy4rlVE9Sx_4,5054
|
21
|
-
Tools/web_search.py,sha256=4EGq1VZqfDgG-_yXTd4_Ha1iEUcR-szdlgRV7oFPru4,1259
|
22
|
-
Utils/__init__.py,sha256=oukU0ufroPRd8_N8d2xiFes9CTxSaw4NA6p2nS1kkSg,16
|
23
|
-
Utils/executor_utils.py,sha256=eVVxZDcAGxENRV5DhCkgh1AKiJEgfV_1q-iSQNWR5kI,1180
|
24
|
-
Utils/ter_interface.py,sha256=Zay9mwyAyKYTNQAKOWXHAa3upo9TWprSf26STiHXk0g,6255
|
25
|
-
llm_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
26
|
-
llm_interface/llm.py,sha256=tI_KDOW14QLWowA7bB3GPe2qjlk0sjS5fBavs9XD1fo,5185
|
27
|
-
pikoai-0.1.6.dist-info/licenses/LICENSE,sha256=cELUVOboOAderKFp8bdtcM5VyJi61YH1oDbRhOuoQZw,1067
|
28
|
-
pikoai-0.1.6.dist-info/METADATA,sha256=3EMhRNO8Abcy9dy0QUJEwYYJvajnvbqCmqqhccpD8es,2961
|
29
|
-
pikoai-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
30
|
-
pikoai-0.1.6.dist-info/entry_points.txt,sha256=xjZnheDymNDnQ0o84R0jZKEITrhNbzQWN-AhqfA_d6s,50
|
31
|
-
pikoai-0.1.6.dist-info/top_level.txt,sha256=hWzBNE7UQsuNcENIOksGcJED08k3ZGRRn2X5jnStICU,53
|
32
|
-
pikoai-0.1.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|