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.
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/PKG-INFO +25 -1
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/README.md +24 -0
- mini_code_cli-0.0.8.2/mini_code_cli/agent_loop.py +360 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli/prompts.py +1 -1
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli.egg-info/PKG-INFO +25 -1
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/pyproject.toml +1 -1
- mini_code_cli-0.0.8.0/mini_code_cli/agent_loop.py +0 -309
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/LICENSE +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli/__init__.py +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli/tools.py +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli/utils.py +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli.egg-info/SOURCES.txt +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli.egg-info/dependency_links.txt +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli.egg-info/entry_points.txt +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/mini_code_cli.egg-info/top_level.txt +0 -0
- {mini_code_cli-0.0.8.0 → mini_code_cli-0.0.8.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mini-code-cli
|
|
3
|
-
Version: 0.0.8.
|
|
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.
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|