mini-code-cli 0.0.8.0__tar.gz → 0.0.8.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-code-cli
3
- Version: 0.0.8.0
3
+ Version: 0.0.8.2
4
4
  Summary: Minimal Coding CLI
5
5
  Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -63,6 +63,30 @@ Write an agent.md file for me that helps an LLM write efficient triton kernels,
63
63
  ```bash
64
64
  mini-code --help
65
65
  ```
66
+ ```
67
+ Agent loop for interacting with a language model
68
+
69
+ options:
70
+ -h, --help show this help message and exit
71
+ --url URL API host
72
+ --max-tokens MAX_TOKENS
73
+ Maximum tokens for LLM response
74
+ --temperature TEMPERATURE
75
+ Temperature for LLM
76
+ --model MODEL Model name
77
+ --api-key API_KEY API key for authentication. If not set, it tries to find it in env variable: MINI_CODE_API_KEY.
78
+ --cache-dir CACHE_DIR
79
+ The cache dir for the session histories.
80
+ --system-prompt SYSTEM_PROMPT
81
+ Replace system prompt, with a custom system prompt.
82
+ --auto-mode Whether to run the agent in `auto-mode'. Or default: `manual-mode'.
83
+ --agent-md AGENT_MD If the agent should use an agent-md file... (it will added after system message.)
84
+ --prompt PROMPT An initial prompt from the user...
85
+ --ask-permission Ask for permission before any tool call.
86
+ --allowed-dir ALLOWED_DIR
87
+ Allowed directory for file operations
88
+ --enable-shell Allow shell execution. Default: False
89
+ ```
66
90
 
67
91
 
68
92
  ## (C) Nikolai Rozanov, 2026 - Present
@@ -43,6 +43,30 @@ Write an agent.md file for me that helps an LLM write efficient triton kernels,
43
43
  ```bash
44
44
  mini-code --help
45
45
  ```
46
+ ```
47
+ Agent loop for interacting with a language model
48
+
49
+ options:
50
+ -h, --help show this help message and exit
51
+ --url URL API host
52
+ --max-tokens MAX_TOKENS
53
+ Maximum tokens for LLM response
54
+ --temperature TEMPERATURE
55
+ Temperature for LLM
56
+ --model MODEL Model name
57
+ --api-key API_KEY API key for authentication. If not set, it tries to find it in env variable: MINI_CODE_API_KEY.
58
+ --cache-dir CACHE_DIR
59
+ The cache dir for the session histories.
60
+ --system-prompt SYSTEM_PROMPT
61
+ Replace system prompt, with a custom system prompt.
62
+ --auto-mode Whether to run the agent in `auto-mode'. Or default: `manual-mode'.
63
+ --agent-md AGENT_MD If the agent should use an agent-md file... (it will added after system message.)
64
+ --prompt PROMPT An initial prompt from the user...
65
+ --ask-permission Ask for permission before any tool call.
66
+ --allowed-dir ALLOWED_DIR
67
+ Allowed directory for file operations
68
+ --enable-shell Allow shell execution. Default: False
69
+ ```
46
70
 
47
71
 
48
72
  ## (C) Nikolai Rozanov, 2026 - Present
@@ -0,0 +1,360 @@
1
+ """
2
+ Usage:
3
+ mini-code
4
+
5
+ Prompt:
6
+
7
+ write a similar model.py file but for a LSTM based language model. Make it minimal and call it model_lstm.py it should still be compatible with eval.py
8
+
9
+ """
10
+
11
+ import os
12
+ import json
13
+ import signal
14
+
15
+ import time
16
+ import uuid
17
+
18
+ import argparse
19
+ import sys
20
+
21
+ from . import prompts
22
+ from . import tools
23
+ from . import utils
24
+
25
+
26
+ # ANSI escape codes for colors and bold
27
+ BOLD = '\033[1m'
28
+ RED = '\033[91m'
29
+ GREEN = '\033[92m'
30
+ YELLOW = '\033[93m'
31
+ BLUE = '\033[94m'
32
+ MAGENTA = '\033[95m'
33
+ CYAN = '\033[96m'
34
+ RESET = '\033[0m'
35
+
36
+ def save_history(messages, args, session_name=""):
37
+ # Save messages to history.json
38
+ if args.cache_dir:
39
+ cache_dir = args.cache_dir
40
+ else:
41
+ cache_dir = os.path.expanduser("~/.cache/mini_code/sessions/")
42
+ os.makedirs(cache_dir, exist_ok=True)
43
+ history_path = os.path.join(cache_dir, f"history_{session_name}.json")
44
+ with open(history_path, 'w') as f:
45
+ json.dump(messages, f, indent=2)
46
+
47
+ def generate_unique_id():
48
+ """Generate a unique ID based on current time and a random component."""
49
+ timestamp = int(time.time() * 1000000) # microseconds
50
+ return f"{timestamp}-{uuid.uuid4().hex[:8]}"
51
+
52
+ def parse_args():
53
+ parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
54
+ # MODEL / LLM related
55
+ parser.add_argument("--url", type=str, default="http://0.0.0.0:30000", help="API host")
56
+ parser.add_argument("--max-tokens", type=int, help="Maximum tokens for LLM response")
57
+ parser.add_argument("--temperature", type=float, default=0.0, help="Temperature for LLM")
58
+ parser.add_argument("--model", type=str, default="default", help="Model name")
59
+ parser.add_argument("--api-key", type=str, default=None, help="API key for authentication. If not set, it tries to find it in env variable: MINI_CODE_API_KEY.")
60
+
61
+ # Other settings:
62
+ parser.add_argument("--cache-dir", type=str, help="The cache dir for the session histories.")
63
+
64
+ # Agent behaviour related
65
+ parser.add_argument("--system-prompt", type=str, help="Replace system prompt, with a custom system prompt.")
66
+ parser.add_argument("--auto-mode", action="store_true", help="Whether to run the agent in `auto-mode'. Or default: `manual-mode'.")
67
+ parser.add_argument("--agent-md", type=str, help="If the agent should use an agent-md file... (it will added after system message.)")
68
+ parser.add_argument("--prompt", type=str, help="An initial prompt from the user...")
69
+
70
+ # Agent permissions related
71
+ parser.add_argument("--ask-permission", action="store_true", help="Ask for permission before any tool call.")
72
+ parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations")
73
+ parser.add_argument("--enable-shell", action="store_true", help="Allow shell execution. Default: False") #todo
74
+
75
+
76
+ args = parser.parse_args()
77
+ return args
78
+
79
+
80
+ def print_welcome(allowed_dir, server_url, args, unique_id, cache_dir):
81
+ # Calculate the length of the longest line inside the box
82
+ version = __import__('mini_code_cli').__version__
83
+
84
+ permission_mode = "All tool calls require permission." if args.ask_permission else "Tool calls will execute without asking for permission."
85
+ agent_mode = "Agent is in manual-mode." if not args.auto_mode else "Agent is in auto-mode."
86
+ shell_mode = "Shell disabled." if not args.enable_shell else "Shell enabled."
87
+
88
+ lines = [
89
+ f" MiniCodeCLI (v{version})",
90
+ f" The session-id is: {unique_id}" ,
91
+ f" It will be saved under: `{cache_dir}`",
92
+ " Type 'quit' or 'exit' to exit the loop.",
93
+ f" Allowed dir: {allowed_dir}",
94
+ f" Server URL: {server_url}",
95
+ f" {permission_mode}",
96
+ f" {agent_mode}",
97
+ f" {shell_mode}"
98
+ ]
99
+ max_len = max(len(line) for line in lines)
100
+ margin = 2 # Space on each side inside the box
101
+ inner_width = max_len + 2 * margin
102
+ # Build the box
103
+ top_bottom = "╔" + "═" * inner_width + "╗"
104
+ print(top_bottom)
105
+ for line in lines:
106
+ # Pad the line to inner_width characters
107
+ padded = line.ljust(inner_width)
108
+ print(f"║{padded}║")
109
+ bottom = "╚" + "═" * inner_width + "╝"
110
+ print(bottom)
111
+
112
+ def print_help(allowed_dir, server_url, args, unique_id, cache_dir):
113
+ print_welcome(allowed_dir, server_url, args, unique_id, cache_dir)
114
+
115
+ help_message = ""
116
+ help_message += "Manual-mode means the agent will ask for user input after each LLM prediction." if not args.auto_mode else "Auto-mode means the agent will keep calling the LLM until no further LLM calls are possible."
117
+
118
+ print(f"{BOLD}{GREEN}HELP:{RESET} {help_message}")
119
+
120
+ def call_function_with_timeout(func, tool_params, name):
121
+ """calls function with params"""
122
+ class TimeoutError(Exception):
123
+ pass
124
+
125
+ def timeout_handler(signum, frame):
126
+ raise TimeoutError("Function call timed out")
127
+
128
+ timeout_seconds = 30 # Set your desired timeout in seconds
129
+ signal.signal(signal.SIGALRM, timeout_handler)
130
+ signal.alarm(timeout_seconds)
131
+ try:
132
+ result = func(**tool_params)
133
+ except TimeoutError:
134
+ print(f"{BOLD}{RED}Error:{RESET} Function {name} timed out after {timeout_seconds} seconds.")
135
+ result = "Error:Function {name} timed out after {timeout_seconds} seconds."
136
+ finally:
137
+ signal.alarm(0) # Disable the alarm
138
+
139
+ return result
140
+
141
+
142
+
143
+ def main():
144
+ args = parse_args()
145
+
146
+ # Get session unique ID:
147
+ unique_id = generate_unique_id()
148
+ cache_dir = args.cache_dir if args.cache_dir else "~/.cache/mini_code/sessions/"
149
+
150
+ # LLM Related
151
+ server_url = f"{args.url}"
152
+ api_key = None
153
+ if args.api_key:
154
+ api_key = args.api_key
155
+ else:
156
+ api_key = os.environ.get("MINI_CODE_API_KEY", "")
157
+ if api_key:
158
+ print(f"{YELLOW}API Key found at MINI_CODE_API_KEY{RESET}")
159
+ else:
160
+ print(f"{YELLOW}NO API Key found at MINI_CODE_API_KEY. Set it with export MINI_CODE_API_KEY='sk-api-key'{RESET}")
161
+
162
+
163
+ # Permissions related
164
+ allowed_dir = os.path.abspath(args.allowed_dir)
165
+
166
+ if not args.enable_shell:
167
+ forbidden_tools = tools.shell_tools
168
+ else:
169
+ forbidden_tools = None
170
+
171
+ # TOOLs
172
+ tools_dict = tools.util_get_tools_dict(forbidden_tools=forbidden_tools)
173
+ llm_tools_dict = tools.util_get_tools(forbidden_tools=forbidden_tools)
174
+
175
+
176
+ # System Message and Agent.md
177
+ if args.system_prompt:
178
+ system_prompt = args.system_prompt
179
+ else:
180
+ system_prompt = prompts.system_prompt(tools_dict=tools_dict, allowed_dir=allowed_dir)
181
+
182
+ messages = [{"role": "system", "content": system_prompt}]
183
+
184
+ if args.agent_md:
185
+ print(f"{YELLOW}AGENT.md '{args.agent_md}' specified.{RESET}")
186
+
187
+ # Check if the file exists
188
+ if not os.path.isfile(args.agent_md):
189
+ print(f"{RED}Error: The AGENT.md '{args.agent_md}' does not exist.{RESET}")
190
+ else:
191
+ # Load the content of agent-md file and add it after system message
192
+ with open(args.agent_md, 'r', encoding='utf-8') as f:
193
+ agent_md_content = f.read()
194
+ print(f"{YELLOW}AGENT.md '{args.agent_md}' loaded.{RESET}")
195
+
196
+ # Append the content as a user message after the system prompt
197
+ messages.append({"role": "user", "content": agent_md_content})
198
+
199
+ print_welcome(allowed_dir, server_url, args, unique_id, cache_dir)
200
+
201
+ # Agent Logic Flags
202
+ llm_response_flag = False
203
+ llm_tool_response_flag = False
204
+ llm_repeat_flag = False
205
+ tool_count = 0
206
+
207
+ user_input = args.prompt
208
+
209
+ while True:
210
+ try:
211
+
212
+ if llm_tool_response_flag and args.auto_mode: #skip user input and try to call the model again until there no more tool calls.
213
+ print(f"{BOLD}{YELLOW}System:{RESET} Skipping user input. Continuing with LLM calls until no more tool calls. [Tool count={tool_count}]")
214
+ llm_repeat_flag = True
215
+ else:
216
+ # get user input
217
+ if llm_repeat_flag:
218
+ print(f"{BOLD}{YELLOW}System:{RESET} Called {tool_count} tools in total. Exiting tool loop.")
219
+
220
+ tool_count = 0
221
+ if not user_input:
222
+ try:
223
+ print("-"*30)
224
+ user_input = input(f"{BOLD}{CYAN}You:{RESET} ")
225
+ if user_input.lower() in ["quit", "exit", "/quit", "/exit"]:
226
+ print("\nThank you. Goodbye!")
227
+ print(f"Saving history to: {cache_dir}/{unique_id}")
228
+ save_history(messages, args, unique_id)
229
+ break
230
+ except KeyboardInterrupt:
231
+ print("\nThank you. Goodbye!")
232
+ print(f"Saving history to: {cache_dir}/{unique_id}")
233
+ save_history(messages, args, unique_id)
234
+ break
235
+ else:
236
+ print("-"*30)
237
+ print(f"{BOLD}{CYAN}You (preset): {RESET}{user_input}")
238
+
239
+ if user_input == "/help":
240
+ print_help(allowed_dir, server_url, args, unique_id, cache_dir)
241
+ user_input = ""
242
+ continue
243
+
244
+ messages.append({"role": "user", "content": user_input})
245
+ user_input = ""
246
+
247
+ llm_response_flag = False
248
+ llm_tool_response_flag = False
249
+
250
+ response_text, tool_calls = utils.call_openai_server(
251
+ messages,
252
+ max_tokens=args.max_tokens,
253
+ temperature=args.temperature,
254
+ model=args.model,
255
+ server_url=server_url,
256
+ tools=llm_tools_dict,
257
+ api_key=api_key,
258
+ )
259
+
260
+ if response_text:
261
+ llm_response_flag = True
262
+ print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
263
+
264
+ if tool_calls:
265
+ llm_tool_response_flag = True
266
+
267
+ messages.append({
268
+ "role": "assistant",
269
+ "content": response_text,
270
+ "tool_calls": tool_calls
271
+ })
272
+ # Tool call parsing and execution
273
+ print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
274
+
275
+ for call in tool_calls:
276
+ tool_count += 1
277
+
278
+ call_id = call.get("id", "")
279
+ name = call.get("function", {}).get("name", "")
280
+ params_str = call.get("function", {}).get("arguments", "{}")
281
+ try:
282
+ tool_params = json.loads(params_str)
283
+ except json.JSONDecodeError:
284
+ print(f"{BOLD}{RED}Warning:{RESET} Error parsing arguments for tool {name}")
285
+ continue
286
+
287
+ if name in tools_dict:
288
+ # Check allowed directory for file operations
289
+ if name in ["read_file", "write_file", "grep", "glob"]:
290
+ filepath = tool_params.get("filepath", "")
291
+ if name == "write_file":
292
+ filepath = tool_params.get("filepath", "")
293
+ if filepath and not os.path.abspath(filepath).startswith(allowed_dir):
294
+ print(f"{BOLD}{RED}Warning:{RESET} File operation not allowed outside allowed directory: {allowed_dir}")
295
+ continue
296
+
297
+ func = tools_dict[name]["function"]
298
+ # asking permissions
299
+ if args.ask_permission:
300
+ print(f"{BOLD}{YELLOW}Tool permission:{RESET} Are you sure you want to call the tool?")
301
+ print(f"name: {name}, parameters: {params_str}")
302
+ try:
303
+ permission_input = input("Type: 'y' or 'yes'\n")
304
+ except KeyboardInterrupt:
305
+ print("\nThank you. Goodbye! Saving history...")
306
+ save_history(messages, args, unique_id)
307
+ sys.exit(0)
308
+
309
+ if not permission_input in ['y', 'yes']:
310
+ print(f"{BOLD}{YELLOW}Tool permission denied:{RESET}.")
311
+ messages.append({
312
+ "role": "user",
313
+ "content": f"User denied permission for tool {name} and params {params_str}."
314
+ })
315
+ continue
316
+
317
+ print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call: {name} with {tool_params}.")
318
+ result = call_function_with_timeout(func, tool_params, name)
319
+ # Limiting output to 300 characters for readability
320
+ result_str = str(result)
321
+ if len(result_str) > 300:
322
+ result_str = result_str[:300] + "... (truncated to 300 chars)"
323
+ print(f"{BOLD}{MAGENTA}Tool '{name}' returned:{RESET} {result_str}")
324
+
325
+ messages.append({
326
+ "role": "tool",
327
+ "tool_call_id": call_id,
328
+ "content": str(result)
329
+ })
330
+ else:
331
+ print(f"{BOLD}{RED}Error:{RESET} Unknown tool: {name}, params {params_str}")
332
+ messages.append({
333
+ "role": "tool",
334
+ "tool_call_id": call_id,
335
+ "content": f"Error: Unknown tool: {name}, params {params_str}"
336
+ })
337
+ else:
338
+ messages.append({
339
+ "role": "assistant",
340
+ "content": response_text,
341
+ # "tool_calls": tool_calls
342
+ })
343
+
344
+ if not llm_response_flag and not llm_tool_response_flag:
345
+ print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
346
+
347
+ save_history(messages, args, unique_id)
348
+
349
+ except KeyboardInterrupt:
350
+ print(f"{BOLD}{RED}INTERRUPTING:{RESET} Mini Code Exiting...")
351
+
352
+ except Exception as e:
353
+ print(f"{BOLD}{RED}FATAL ERROR:{RESET} Mini Code Crashed...:\n{e}")
354
+
355
+ finally:
356
+ print(f"Saving history to: {cache_dir}/{unique_id}")
357
+ save_history(messages, args, unique_id)
358
+
359
+ if __name__ == "__main__":
360
+ main()
@@ -6,7 +6,7 @@ def system_prompt(tools_dict=None, allowed_dir=None):
6
6
 
7
7
  if tools_dict:
8
8
  tool_descriptions = "\n".join([f"{name} : {data['description']}" for name, data in tools_dict.items()])
9
- base_prompt = "You are a helpful AI assistant with access to the following tools:\n"
9
+ base_prompt = "You are a helpful AI assistant with access to the following tools (only):\n"
10
10
  end_prompt = (
11
11
  "Choose the most appropriate tool for the task at hand. "
12
12
  "If a tool fails or you hit a limit, analyze the error and try a different approach. "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-code-cli
3
- Version: 0.0.8.0
3
+ Version: 0.0.8.2
4
4
  Summary: Minimal Coding CLI
5
5
  Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -63,6 +63,30 @@ Write an agent.md file for me that helps an LLM write efficient triton kernels,
63
63
  ```bash
64
64
  mini-code --help
65
65
  ```
66
+ ```
67
+ Agent loop for interacting with a language model
68
+
69
+ options:
70
+ -h, --help show this help message and exit
71
+ --url URL API host
72
+ --max-tokens MAX_TOKENS
73
+ Maximum tokens for LLM response
74
+ --temperature TEMPERATURE
75
+ Temperature for LLM
76
+ --model MODEL Model name
77
+ --api-key API_KEY API key for authentication. If not set, it tries to find it in env variable: MINI_CODE_API_KEY.
78
+ --cache-dir CACHE_DIR
79
+ The cache dir for the session histories.
80
+ --system-prompt SYSTEM_PROMPT
81
+ Replace system prompt, with a custom system prompt.
82
+ --auto-mode Whether to run the agent in `auto-mode'. Or default: `manual-mode'.
83
+ --agent-md AGENT_MD If the agent should use an agent-md file... (it will added after system message.)
84
+ --prompt PROMPT An initial prompt from the user...
85
+ --ask-permission Ask for permission before any tool call.
86
+ --allowed-dir ALLOWED_DIR
87
+ Allowed directory for file operations
88
+ --enable-shell Allow shell execution. Default: False
89
+ ```
66
90
 
67
91
 
68
92
  ## (C) Nikolai Rozanov, 2026 - Present
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "mini-code-cli"
3
3
  # version = { file = "mini_code_cli/__init__.py", attr = "__version__" }
4
- version = "0.0.8.0"
4
+ version = "0.0.8.2"
5
5
  description = "Minimal Coding CLI"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.10"
@@ -1,309 +0,0 @@
1
- """
2
- Usage:
3
- mini-code
4
-
5
- Prompt:
6
-
7
- write a similar model.py file but for a LSTM based language model. Make it minimal and call it model_lstm.py it should still be compatible with eval.py
8
-
9
- """
10
-
11
- import os
12
- import json
13
- import signal
14
-
15
-
16
- from . import prompts
17
- from . import tools
18
- from . import utils
19
-
20
- import argparse
21
- import sys
22
-
23
- # ANSI escape codes for colors and bold
24
- BOLD = '\033[1m'
25
- RED = '\033[91m'
26
- GREEN = '\033[92m'
27
- YELLOW = '\033[93m'
28
- BLUE = '\033[94m'
29
- MAGENTA = '\033[95m'
30
- CYAN = '\033[96m'
31
- RESET = '\033[0m'
32
-
33
- def save_history(messages):
34
- # Save messages to history.json
35
- with open("history.json", "w") as f:
36
- json.dump(messages, f, indent=2)
37
-
38
-
39
- def parse_args():
40
- parser = argparse.ArgumentParser(description="Agent loop for interacting with a language model")
41
- # MODEL / LLM related
42
- parser.add_argument("--url", type=str, default="http://0.0.0.0:30000", help="API host")
43
- parser.add_argument("--max-tokens", type=int, help="Maximum tokens for LLM response")
44
- parser.add_argument("--temperature", type=float, default=0.0, help="Temperature for LLM")
45
- parser.add_argument("--model", type=str, default="default", help="Model name")
46
- parser.add_argument("--api-key", type=str, default=None, help="API key for authentication. If not set, it tries to find it in env variable: MINI_CODE_API_KEY.")
47
-
48
- # Agent behaviour related
49
- parser.add_argument("--system-prompt", type=str, help="Replace system prompt, with a custom system prompt.")
50
- parser.add_argument("--auto-mode", action="store_true", help="Whether to run the agent in `auto-mode'. Or default: `manual-mode'.")
51
- parser.add_argument("--agent-md", type=str, help="If the agent should use an agent-md file... (it will added after system message.)")
52
-
53
- # Agent permissions related
54
- parser.add_argument("--ask-permission", action="store_true", help="Ask for permission before any tool call.")
55
- parser.add_argument("--allowed-dir", type=str, default=".", help="Allowed directory for file operations")
56
- parser.add_argument("--enable-shell", action="store_true", help="Allow shell execution. Default: False") #todo
57
-
58
-
59
- args = parser.parse_args()
60
- return args
61
-
62
-
63
- def print_welcome(allowed_dir, server_url, args):
64
- # Calculate the length of the longest line inside the box
65
- version = __import__('mini_code_cli').__version__
66
-
67
- permission_mode = "All tool calls require permission." if args.ask_permission else "Tool calls will execute without asking for permission."
68
- agent_mode = "Agent is in manual-mode." if not args.auto_mode else "Agent is in auto-mode."
69
- shell_mode = "Shell disabled." if not args.enable_shell else "Shell enabled."
70
-
71
- lines = [
72
- f" MiniCodeCLI (v{version})",
73
- " Type 'quit' or 'exit' to exit the loop.",
74
- f" Allowed dir: {allowed_dir}",
75
- f" Server URL: {server_url}",
76
- f" {permission_mode}",
77
- f" {agent_mode}",
78
- f" {shell_mode}"
79
- ]
80
- max_len = max(len(line) for line in lines)
81
- margin = 2 # Space on each side inside the box
82
- inner_width = max_len + 2 * margin
83
- # Build the box
84
- top_bottom = "╔" + "═" * inner_width + "╗"
85
- print(top_bottom)
86
- for line in lines:
87
- # Pad the line to inner_width characters
88
- padded = line.ljust(inner_width)
89
- print(f"║{padded}║")
90
- bottom = "╚" + "═" * inner_width + "╝"
91
- print(bottom)
92
-
93
- def print_help(allowed_dir, server_url, args):
94
- print_welcome(allowed_dir, server_url, args)
95
-
96
- help_message = ""
97
- help_message += "Manual-mode means the agent will ask for user input after each LLM prediction." if not args.auto_mode else "Auto-mode means the agent will keep calling the LLM until no further LLM calls are possible."
98
-
99
- print(f"{BOLD}{GREEN}HELP:{RESET} {help_message}")
100
-
101
- def call_function_with_timeout(func, tool_params, name):
102
- """calls function with params"""
103
- class TimeoutError(Exception):
104
- pass
105
-
106
- def timeout_handler(signum, frame):
107
- raise TimeoutError("Function call timed out")
108
-
109
- timeout_seconds = 30 # Set your desired timeout in seconds
110
- signal.signal(signal.SIGALRM, timeout_handler)
111
- signal.alarm(timeout_seconds)
112
- try:
113
- result = func(**tool_params)
114
- except TimeoutError:
115
- print(f"{BOLD}{RED}Error:{RESET} Function {name} timed out after {timeout_seconds} seconds.")
116
- result = "Error:Function {name} timed out after {timeout_seconds} seconds."
117
- finally:
118
- signal.alarm(0) # Disable the alarm
119
-
120
- return result
121
-
122
- def main():
123
- args = parse_args()
124
-
125
-
126
- # LLM Related
127
- server_url = f"{args.url}"
128
- api_key = None
129
- if args.api_key:
130
- api_key = args.api_key
131
- else:
132
- api_key = os.environ.get("MINI_CODE_API_KEY", "")
133
- if api_key:
134
- print(f"{YELLOW}API Key found at MINI_CODE_API_KEY{RESET}")
135
- else:
136
- print(f"{YELLOW}NO API Key found at MINI_CODE_API_KEY. Set it with export MINI_CODE_API_KEY='sk-api-key'{RESET}")
137
-
138
-
139
- # Permissions related
140
- allowed_dir = os.path.abspath(args.allowed_dir)
141
-
142
- if not args.enable_shell:
143
- forbidden_tools = tools.shell_tools
144
- else:
145
- forbidden_tools = None
146
-
147
- # TOOLs
148
- tools_dict = tools.util_get_tools_dict(forbidden_tools=forbidden_tools)
149
- llm_tools_dict = tools.util_get_tools(forbidden_tools=forbidden_tools)
150
-
151
-
152
- # System Message and Agent.md
153
- if args.system_prompt:
154
- system_prompt = args.system_prompt
155
- else:
156
- system_prompt = prompts.system_prompt(tools_dict=tools_dict, allowed_dir=allowed_dir)
157
-
158
- messages = [{"role": "system", "content": system_prompt}]
159
-
160
- if args.agent_md:
161
- print(f"{YELLOW}AGENT.md '{args.agent_md}' specified.{RESET}")
162
-
163
- # Check if the file exists
164
- if not os.path.isfile(args.agent_md):
165
- print(f"{RED}Error: The AGENT.md '{args.agent_md}' does not exist.{RESET}")
166
- else:
167
- # Load the content of agent-md file and add it after system message
168
- with open(args.agent_md, 'r', encoding='utf-8') as f:
169
- agent_md_content = f.read()
170
- print(f"{YELLOW}AGENT.md '{args.agent_md}' loaded.{RESET}")
171
-
172
- # Append the content as a user message after the system prompt
173
- messages.append({"role": "user", "content": agent_md_content})
174
-
175
- print_welcome(allowed_dir, server_url, args)
176
-
177
- # Agent Logic Flags
178
- llm_response_flag = False
179
- llm_tool_response_flag = False
180
- llm_repeat_flag = False
181
- tool_count = 0
182
-
183
- while True:
184
- if llm_tool_response_flag and args.auto_mode: #skip user input and try to call the model again until there no more tool calls.
185
- print(f"{BOLD}{YELLOW}System:{RESET} Skipping user input. Continuing with LLM calls until no more tool calls. [Tool count={tool_count}]")
186
- llm_repeat_flag = True
187
- else:
188
- # get user input
189
- if llm_repeat_flag:
190
- print(f"{BOLD}{YELLOW}System:{RESET} Called {tool_count} tools in total. Exiting tool loop.")
191
-
192
- tool_count = 0
193
- try:
194
- print("-"*30)
195
- user_input = input(f"{BOLD}{CYAN}You:{RESET} ")
196
- if user_input.lower() in ["quit", "exit", "/quit", "/exit"]:
197
- print("\nThank you. Goodbye! Saving history...")
198
- save_history(messages)
199
- break
200
- except KeyboardInterrupt:
201
- print("\nThank you. Goodbye! Saving history...")
202
- save_history(messages)
203
- break
204
-
205
- if user_input == "/help":
206
- print_help(allowed_dir, server_url, args)
207
- continue
208
-
209
- messages.append({"role": "user", "content": user_input})
210
-
211
- llm_response_flag = False
212
- llm_tool_response_flag = False
213
-
214
- response_text, tool_calls = utils.call_openai_server(
215
- messages,
216
- max_tokens=args.max_tokens,
217
- temperature=args.temperature,
218
- model=args.model,
219
- server_url=server_url,
220
- tools=llm_tools_dict,
221
- api_key=api_key,
222
- )
223
-
224
- if response_text:
225
- llm_response_flag = True
226
- print(f"{BOLD}{GREEN}Assistant:{RESET} {response_text}")
227
-
228
- if tool_calls:
229
- llm_tool_response_flag = True
230
-
231
- messages.append({
232
- "role": "assistant",
233
- "content": response_text,
234
- "tool_calls": tool_calls
235
- })
236
- # Tool call parsing and execution
237
- print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call {len(tool_calls)} tools.")
238
-
239
- for call in tool_calls:
240
- tool_count += 1
241
-
242
- call_id = call.get("id", "")
243
- name = call.get("function", {}).get("name", "")
244
- params_str = call.get("function", {}).get("arguments", "{}")
245
- try:
246
- tool_params = json.loads(params_str)
247
- except json.JSONDecodeError:
248
- print(f"{BOLD}{RED}Warning:{RESET} Error parsing arguments for tool {name}")
249
- continue
250
-
251
- if name in tools_dict:
252
- # Check allowed directory for file operations
253
- if name in ["read_file", "write_file", "grep", "glob"]:
254
- filepath = tool_params.get("filepath", "")
255
- if name == "write_file":
256
- filepath = tool_params.get("filepath", "")
257
- if filepath and not os.path.abspath(filepath).startswith(allowed_dir):
258
- print(f"{BOLD}{RED}Warning:{RESET} File operation not allowed outside allowed directory: {allowed_dir}")
259
- continue
260
-
261
- func = tools_dict[name]["function"]
262
- # asking permissions
263
- if args.ask_permission:
264
- print(f"{BOLD}{YELLOW}Tool permission:{RESET} Are you sure you want to call the tool?")
265
- print(f"name: {name}, parameters: {params_str}")
266
- try:
267
- permission_input = input("Type: 'y' or 'yes'\n")
268
- except KeyboardInterrupt:
269
- print("\nThank you. Goodbye! Saving history...")
270
- save_history(messages)
271
- sys.exit(0)
272
-
273
- if not permission_input in ['y', 'yes']:
274
- print(f"{BOLD}{YELLOW}Tool permission denied:{RESET}.")
275
- messages.append({
276
- "role": "user",
277
- "content": f"User denied permission for tool {name} and params {params_str}."
278
- })
279
- continue
280
-
281
- print(f"{BOLD}{YELLOW}Tool Call:{RESET} LLM is trying to call: {name} with {tool_params}.")
282
- result = call_function_with_timeout(func, tool_params, name)
283
- # Limiting output to 300 characters for readability
284
- result_str = str(result)
285
- if len(result_str) > 300:
286
- result_str = result_str[:300] + "... (truncated to 300 chars)"
287
- print(f"{BOLD}{MAGENTA}Tool '{name}' returned:{RESET} {result_str}")
288
-
289
- messages.append({
290
- "role": "tool",
291
- "tool_call_id": call_id,
292
- "content": str(result)
293
- })
294
- else:
295
- print(f"{BOLD}{RED}Warning:{RESET} Unknown tool: {name}, params {params_str}")
296
- else:
297
- messages.append({
298
- "role": "assistant",
299
- "content": response_text,
300
- # "tool_calls": tool_calls
301
- })
302
-
303
- if not llm_response_flag and not llm_tool_response_flag:
304
- print(f"{BOLD}{RED}Warning:{RESET} No response was given by the LLM.")
305
-
306
- save_history(messages)
307
-
308
- if __name__ == "__main__":
309
- main()
File without changes