AGofficial commited on
Commit
81d22e5
·
verified ·
1 Parent(s): 8b0039c

Upload agminicli.py

Browse files
Files changed (1) hide show
  1. agminicli.py +222 -0
agminicli.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import glob as globlib, json, os, re, subprocess, openai, inspect
2
+ from datetime import datetime
3
+
4
+ MODEL = "claude-haiku-4.5"
5
+ RESET, BOLD, DIM = "\033[0m", "\033[1m", "\033[2m"
6
+ BLUE, CYAN, GREEN, YELLOW, RED = (
7
+ "\033[34m",
8
+ "\033[36m",
9
+ "\033[32m",
10
+ "\033[33m",
11
+ "\033[31m",
12
+ )
13
+
14
+ CORE_TOOLS = {}
15
+ HIDDEN_TOOLS = {}
16
+ ACTIVATED_TOOLS = set()
17
+
18
+ def get_schema(f):
19
+ """Generate OpenAI tool schema from function signature and docstring"""
20
+ sig = inspect.signature(f)
21
+ doc = inspect.getdoc(f) or ""
22
+ properties = {}
23
+ required = []
24
+ for name, param in sig.parameters.items():
25
+ p_type = "string"
26
+ if param.annotation == int or param.annotation == float: p_type = "number"
27
+ elif param.annotation == bool: p_type = "boolean"
28
+ properties[name] = {"type": p_type}
29
+ if param.default is inspect.Parameter.empty: required.append(name)
30
+ return {
31
+ "type": "function",
32
+ "function": {
33
+ "name": f.__name__,
34
+ "description": doc,
35
+ "parameters": {
36
+ "type": "object",
37
+ "properties": properties,
38
+ "required": required,
39
+ },
40
+ },
41
+ }
42
+
43
+ def tool(f):
44
+ """Decorator for hidden tools"""
45
+ HIDDEN_TOOLS[f.__name__] = {"fn": f, "schema": get_schema(f)}
46
+ return f
47
+
48
+ def core_tool(f):
49
+ """Decorator for core tools"""
50
+ CORE_TOOLS[f.__name__] = {"fn": f, "schema": get_schema(f)}
51
+ return f
52
+
53
+ @core_tool
54
+ def search_tools(query: str):
55
+ """Search for hidden tools by fuzzy matching name, description, or parameters"""
56
+ results = []
57
+ q = query.lower()
58
+ for name, data in HIDDEN_TOOLS.items():
59
+ schema = data["schema"]["function"]
60
+ desc = schema.get("description", "").lower()
61
+ if q in name.lower() or q in desc or q in json.dumps(schema.get("parameters", {})):
62
+ results.append(data["schema"])
63
+ ACTIVATED_TOOLS.add(name)
64
+ return json.dumps(results) if results else "No tools found"
65
+
66
+ @core_tool
67
+ def read(path: str, offset: int = 0, limit: int = None):
68
+ """Read file with line numbers"""
69
+ with open(path) as f:
70
+ lines = f.readlines()
71
+ l_limit = limit if limit is not None else len(lines)
72
+ selected = lines[offset : offset + l_limit] if limit else lines[offset:]
73
+ return "".join(f"{offset + idx + 1:4}| {line}" for idx, line in enumerate(selected))
74
+
75
+ @core_tool
76
+ def write(path: str, content: str):
77
+ """Write content to file"""
78
+ with open(path, "w") as f:
79
+ f.write(content)
80
+ return "ok"
81
+
82
+ @core_tool
83
+ def edit(path: str, old: str, new: str, all: bool = False):
84
+ """Replace old with new in file"""
85
+ with open(path) as f:
86
+ text = f.read()
87
+ if old not in text: return "error: old_string not found"
88
+ count = text.count(old)
89
+ if not all and count > 1: return f"error: old_string appears {count} times, must be unique (use all=true)"
90
+ replacement = text.replace(old, new) if all else text.replace(old, new, 1)
91
+ with open(path, "w") as f:
92
+ f.write(replacement)
93
+ return "ok"
94
+
95
+ @core_tool
96
+ def glob(pat: str, path: str = "."):
97
+ """Find files by pattern"""
98
+ pattern = (path + "/" + pat).replace("//", "/")
99
+ files = globlib.glob(pattern, recursive=True)
100
+ files = sorted(files, key=lambda f: os.path.getmtime(f) if os.path.isfile(f) else 0, reverse=True)
101
+ return "\n".join(files) or "none"
102
+
103
+ @core_tool
104
+ def grep(pat: str, path: str = "."):
105
+ """Search files for regex pattern"""
106
+ pattern = re.compile(pat)
107
+ hits = []
108
+ for filepath in globlib.glob(path + "/**", recursive=True):
109
+ try:
110
+ with open(filepath) as f:
111
+ for line_num, line in enumerate(f, 1):
112
+ if pattern.search(line): hits.append(f"{filepath}:{line_num}:{line.rstrip()}")
113
+ except: pass
114
+ return "\n".join(hits[:50]) or "none"
115
+
116
+ @core_tool
117
+ def bash(cmd: str):
118
+ """Run shell command"""
119
+ result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
120
+ return (result.stdout + result.stderr).strip() or "(empty)"
121
+
122
+ @tool
123
+ def get_time():
124
+ """Get current time"""
125
+ return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
126
+
127
+ @tool
128
+ def get_weather():
129
+ """Get current weather information based on the season"""
130
+ now = datetime.now()
131
+ month = now.strftime("%B")
132
+ m = now.month
133
+ if m in [12, 1, 2]: season = "winter"
134
+ elif m in [3, 4, 5]: season = "spring"
135
+ elif m in [6, 7, 8]: season = "summer"
136
+ else: season = "autumn"
137
+ return f"Its {month}, its the {season} season.."
138
+
139
+ def run_tool(name, args):
140
+ try:
141
+ if name in CORE_TOOLS: return CORE_TOOLS[name]["fn"](**args)
142
+ if name in HIDDEN_TOOLS: return HIDDEN_TOOLS[name]["fn"](**args)
143
+ return f"error: tool {name} not found"
144
+ except Exception as err:
145
+ return f"error: {err}"
146
+
147
+ client = openai.OpenAI(
148
+ api_key=os.environ.get("POE_API_KEY", ""),
149
+ base_url="https://api.poe.com/v1",
150
+ )
151
+
152
+ def separator():
153
+ try: cols = os.get_terminal_size().columns
154
+ except: cols = 80
155
+ return f"{DIM}{'─' * min(cols, 80)}{RESET}"
156
+
157
+ def render_markdown(text):
158
+ return re.sub(r"\*\*(.+?)\*\*", f"{BOLD}\\1{RESET}", text)
159
+
160
+ def main():
161
+ print(f"{RED}ag-mini-cli{RESET} | {DIM}{MODEL} | {os.getcwd()}{RESET}\n")
162
+ messages = []
163
+ system_prompt = f"""
164
+ You are a concise coding assistant. cwd: {os.getcwd()}
165
+
166
+ When you are tasked with something that you do not have the tools to do, you should try searching for tools that can help you.
167
+
168
+ Search for tools like weather, extra operations, math, etc.
169
+
170
+ """
171
+
172
+ while True:
173
+ try:
174
+ print(separator())
175
+ user_input = input(f"{BOLD}{BLUE}❯{RESET} ").strip()
176
+ print(separator())
177
+ if not user_input: continue
178
+ if user_input in ("/q", "exit"): break
179
+ if user_input == "/c":
180
+ messages = []
181
+ ACTIVATED_TOOLS.clear()
182
+ print(f"{GREEN}⏺ Cleared conversation{RESET}")
183
+ continue
184
+
185
+ messages.append({"role": "user", "content": user_input})
186
+
187
+ while True:
188
+ current_tools = [data["schema"] for data in CORE_TOOLS.values()]
189
+ current_tools += [HIDDEN_TOOLS[name]["schema"] for name in ACTIVATED_TOOLS if name in HIDDEN_TOOLS]
190
+
191
+ response = client.chat.completions.create(
192
+ model=MODEL,
193
+ messages=[{"role": "system", "content": system_prompt}] + messages,
194
+ tools=current_tools if current_tools else None,
195
+ )
196
+
197
+ resp_msg = response.choices[0].message
198
+ msg_dict = {"role": "assistant", "content": resp_msg.content}
199
+ if resp_msg.tool_calls: msg_dict["tool_calls"] = resp_msg.tool_calls
200
+ messages.append(msg_dict)
201
+
202
+ if resp_msg.content: print(f"\n{CYAN}⏺{RESET} {render_markdown(resp_msg.content)}")
203
+ if not resp_msg.tool_calls: break
204
+
205
+ for tool_call in resp_msg.tool_calls:
206
+ name = tool_call.function.name
207
+ args = json.loads(tool_call.function.arguments)
208
+ arg_preview = str(list(args.values())[0])[:50] if args else ""
209
+ print(f"\n{GREEN}⏺ {name.capitalize()}{RESET}({DIM}{arg_preview}{RESET})")
210
+ result = run_tool(name, args)
211
+ res_lines = str(result).split("\n")
212
+ preview = res_lines[0][:60]
213
+ if len(res_lines) > 1: preview += f" ... +{len(res_lines) - 1} lines"
214
+ elif len(res_lines[0]) > 60: preview += "..."
215
+ print(f" {DIM}⎿ {preview}{RESET}")
216
+ messages.append({"role": "tool", "tool_call_id": tool_call.id, "name": name, "content": str(result)})
217
+ print()
218
+ except (KeyboardInterrupt, EOFError): break
219
+ except Exception as err: print(f"{RED}⏺ Error: {err}{RESET}")
220
+
221
+ if __name__ == "__main__":
222
+ main()