baxter-cli 0.1.0__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.
- baxter/__init__.py +1 -0
- baxter/baxter_cli.py +450 -0
- baxter/providers.py +298 -0
- baxter/terminal_ui.py +630 -0
- baxter/tools/__init__.py +1 -0
- baxter/tools/apply_diff.py +96 -0
- baxter/tools/delete_path.py +34 -0
- baxter/tools/git_cmd.py +130 -0
- baxter/tools/list_dir.py +38 -0
- baxter/tools/make_dir.py +19 -0
- baxter/tools/read_file.py +42 -0
- baxter/tools/registry.py +113 -0
- baxter/tools/run_cmd.py +85 -0
- baxter/tools/safe_path.py +66 -0
- baxter/tools/search_code.py +267 -0
- baxter/tools/write_file.py +40 -0
- baxter_cli-0.1.0.dist-info/METADATA +253 -0
- baxter_cli-0.1.0.dist-info/RECORD +21 -0
- baxter_cli-0.1.0.dist-info/WHEEL +5 -0
- baxter_cli-0.1.0.dist-info/entry_points.txt +2 -0
- baxter_cli-0.1.0.dist-info/top_level.txt +1 -0
baxter/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# entry point for baxter
|
baxter/baxter_cli.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
from baxter import terminal_ui as tui
|
|
9
|
+
from baxter.providers import (
|
|
10
|
+
PROVIDERS,
|
|
11
|
+
call_provider,
|
|
12
|
+
provider_has_key,
|
|
13
|
+
)
|
|
14
|
+
from baxter.tools.registry import TOOL_NAMES, render_registry_for_prompt, run_tool
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_baxter_env() -> None:
|
|
18
|
+
# 1) Load machine-level Baxter config for one-time key setup.
|
|
19
|
+
home = os.path.expanduser("~")
|
|
20
|
+
if home and home != "~":
|
|
21
|
+
user_env = os.path.join(home, ".baxter", ".env")
|
|
22
|
+
if os.path.isfile(user_env):
|
|
23
|
+
load_dotenv(dotenv_path=user_env, override=False)
|
|
24
|
+
|
|
25
|
+
# 2) Load per-project overrides from cwd.
|
|
26
|
+
load_dotenv(override=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
load_baxter_env()
|
|
30
|
+
|
|
31
|
+
BOOT_BANNER = r"""
|
|
32
|
+
██████╗ █████╗ ██╗ ██╗████████╗███████╗██████╗
|
|
33
|
+
██╔══██╗██╔══██╗╚██╗██╔╝╚══██╔══╝██╔════╝██╔══██╗
|
|
34
|
+
██████╔╝███████║ ╚███╔╝ ██║ █████╗ ██████╔╝
|
|
35
|
+
██╔══██╗██╔══██║ ██╔██╗ ██║ ██╔══╝ ██╔══██╗
|
|
36
|
+
██████╔╝██║ ██║██╔╝ ██╗ ██║ ███████╗██║ ██║
|
|
37
|
+
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
|
38
|
+
|
|
39
|
+
⟡ B A X T E R • Neural Reasoning Engine Online ⟡
|
|
40
|
+
──────────────────────────────────────────────────────────
|
|
41
|
+
CORE: STABLE | TOOL MODULES: READY | SAFETY: ON
|
|
42
|
+
──────────────────────────────────────────────────────────
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
MUTATING_TOOLS = {"apply_diff", "write_file", "make_dir", "delete_path", "run_cmd"}
|
|
46
|
+
READ_ONLY_REQUEST_HINTS = (
|
|
47
|
+
"what does",
|
|
48
|
+
"what is in",
|
|
49
|
+
"show me",
|
|
50
|
+
"read ",
|
|
51
|
+
"display ",
|
|
52
|
+
"contents",
|
|
53
|
+
"inside",
|
|
54
|
+
"cat ",
|
|
55
|
+
"view ",
|
|
56
|
+
)
|
|
57
|
+
MUTATING_REQUEST_HINTS = (
|
|
58
|
+
"edit",
|
|
59
|
+
"change",
|
|
60
|
+
"modify",
|
|
61
|
+
"update",
|
|
62
|
+
"fix",
|
|
63
|
+
"rewrite",
|
|
64
|
+
"refactor",
|
|
65
|
+
"create",
|
|
66
|
+
"add",
|
|
67
|
+
"delete",
|
|
68
|
+
"remove",
|
|
69
|
+
"rename",
|
|
70
|
+
"move",
|
|
71
|
+
"commit",
|
|
72
|
+
"push",
|
|
73
|
+
"run ",
|
|
74
|
+
"execute",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_system_prompt() -> str:
|
|
79
|
+
return f"""You are Baxter, a helpful coding assistant.
|
|
80
|
+
|
|
81
|
+
You have OPTIONAL access to a tool registry. Use a tool ONLY when necessary to complete the user's request correctly.
|
|
82
|
+
|
|
83
|
+
{render_registry_for_prompt()}
|
|
84
|
+
|
|
85
|
+
TOOL CALL RULES:
|
|
86
|
+
- If you decide to use a tool, your entire response MUST be ONLY valid JSON on a single line:
|
|
87
|
+
{{"tool":"<tool_name>","args":{{...}}}}
|
|
88
|
+
- tool must be one of: {", ".join(TOOL_NAMES)}
|
|
89
|
+
- Do not include any extra text before or after the JSON (no markdown, no explanation).
|
|
90
|
+
- Return exactly ONE tool call per response. Never return multiple JSON objects.
|
|
91
|
+
- If no tool is needed, respond normally in plain English.
|
|
92
|
+
- If the user asks what is inside a file / to view / open / read / show contents, you MUST call read_file.
|
|
93
|
+
- If the user asks to list a directory, you MUST call list_dir.
|
|
94
|
+
- If the user asks to create a folder/directory, you MUST call make_dir.
|
|
95
|
+
- If the user asks to delete/remove a file or folder, you MUST call delete_path.
|
|
96
|
+
- If the user asks to create a NEW file, you MUST call write_file.
|
|
97
|
+
- If the user asks to change/edit/modify a file, you MUST call read_file first, then apply_diff.
|
|
98
|
+
- If a file path is unknown (example: user only says "edit index.html"), call search_code first with the filename to locate the correct relative path.
|
|
99
|
+
- For search_code, use short search terms (filename, symbol, or key phrase), not the user's entire sentence.
|
|
100
|
+
- Only use write_file with overwrite=true for full rewrites when apply_diff is not suitable.
|
|
101
|
+
- If the user asks to run a terminal command, you MUST call run_cmd (only allowed commands will work).
|
|
102
|
+
- If the user asks to do git actions (status/add/commit/push/pull/etc), you MUST call git_cmd.
|
|
103
|
+
- If the user asks to search the codebase/files for text or symbols, you MUST call search_code.
|
|
104
|
+
- If the user asks to "commit and push" (or equivalent), you MUST do: git add -> git commit -> git push.
|
|
105
|
+
- If the user asks you to commit changes, you MUST run git add and git commit yourself via git_cmd; do not ask the user to run commands.
|
|
106
|
+
- If a commit message is not provided, use a concise default commit message that matches the change.
|
|
107
|
+
- You MUST NOT call git push if there are uncommitted changes.
|
|
108
|
+
- Before any git push, ensure the latest git commit step succeeded (exit_code 0).
|
|
109
|
+
- If replying with instructions, use numbered or bullet points.
|
|
110
|
+
- You MUST NOT claim you created/modified/deleted anything unless a tool result says ok:true.
|
|
111
|
+
- read_file, list_dir, and search_code do not modify files; never claim code was changed after those tools.
|
|
112
|
+
- When the user asks for an edit/fix, keep calling tools until the edit is actually applied (or you are blocked).
|
|
113
|
+
- Never include code blocks or include explanations when calling tools.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def pick_startup_provider() -> str:
|
|
118
|
+
for name in ("anthropic", "openai", "groq"):
|
|
119
|
+
if provider_has_key(name):
|
|
120
|
+
return name
|
|
121
|
+
print(
|
|
122
|
+
tui.c(
|
|
123
|
+
"WARNING: No provider API keys found. "
|
|
124
|
+
"Set one of ANTHROPIC_API_KEY, OPENAI_API_KEY, or GROQ_API_KEY in "
|
|
125
|
+
"~/.baxter/.env (or .env in the current folder).",
|
|
126
|
+
tui.YELLOW,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
return "anthropic"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def try_parse_tool_call(text: str):
|
|
133
|
+
try:
|
|
134
|
+
obj = json.loads(text)
|
|
135
|
+
if isinstance(obj, dict) and "tool" in obj and "args" in obj:
|
|
136
|
+
return obj
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
decoder = json.JSONDecoder()
|
|
141
|
+
i = 0
|
|
142
|
+
while i < len(text):
|
|
143
|
+
start = text.find("{", i)
|
|
144
|
+
if start == -1:
|
|
145
|
+
break
|
|
146
|
+
try:
|
|
147
|
+
obj, end = decoder.raw_decode(text[start:])
|
|
148
|
+
if isinstance(obj, dict) and "tool" in obj and "args" in obj:
|
|
149
|
+
return obj
|
|
150
|
+
i = start + max(1, end)
|
|
151
|
+
except Exception:
|
|
152
|
+
i = start + 1
|
|
153
|
+
|
|
154
|
+
invoke_match = re.search(
|
|
155
|
+
r"<invoke\s+name=\"([^\"]+)\"\s*>(.*?)</invoke>",
|
|
156
|
+
text,
|
|
157
|
+
flags=re.DOTALL | re.IGNORECASE,
|
|
158
|
+
)
|
|
159
|
+
if invoke_match:
|
|
160
|
+
tool_name = invoke_match.group(1).strip()
|
|
161
|
+
invoke_body = invoke_match.group(2)
|
|
162
|
+
args: dict = {}
|
|
163
|
+
for p in re.finditer(
|
|
164
|
+
r"<parameter\s+name=\"([^\"]+)\"\s*>(.*?)</parameter>",
|
|
165
|
+
invoke_body,
|
|
166
|
+
flags=re.DOTALL | re.IGNORECASE,
|
|
167
|
+
):
|
|
168
|
+
key = p.group(1).strip()
|
|
169
|
+
raw_val = p.group(2).strip()
|
|
170
|
+
val: object = raw_val
|
|
171
|
+
low = raw_val.lower()
|
|
172
|
+
if low in {"true", "false"}:
|
|
173
|
+
val = low == "true"
|
|
174
|
+
elif re.fullmatch(r"-?\d+", raw_val):
|
|
175
|
+
try:
|
|
176
|
+
val = int(raw_val)
|
|
177
|
+
except Exception:
|
|
178
|
+
val = raw_val
|
|
179
|
+
elif raw_val.startswith("[") or raw_val.startswith("{"):
|
|
180
|
+
try:
|
|
181
|
+
val = json.loads(raw_val)
|
|
182
|
+
except Exception:
|
|
183
|
+
val = raw_val
|
|
184
|
+
args[key] = val
|
|
185
|
+
if tool_name and isinstance(args, dict):
|
|
186
|
+
return {"tool": tool_name, "args": args}
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def looks_like_broken_tool_call(text: str) -> bool:
|
|
191
|
+
t = (text or "").strip()
|
|
192
|
+
if not t:
|
|
193
|
+
return False
|
|
194
|
+
if '"tool"' in t and '"args"' in t:
|
|
195
|
+
return True
|
|
196
|
+
if t.startswith("{") and ("tool" in t or "args" in t):
|
|
197
|
+
return True
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def last_n_turns(messages, n_turns=6):
|
|
202
|
+
system = messages[0]
|
|
203
|
+
tail = messages[1:]
|
|
204
|
+
trimmed_tail = tail[-(n_turns * 2) :]
|
|
205
|
+
return [system] + trimmed_tail
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def clip(text: str, max_chars: int = 800) -> str:
|
|
209
|
+
if text is None:
|
|
210
|
+
return ""
|
|
211
|
+
raw_limit = os.getenv("BAXTER_CLIP_CHARS", "").strip()
|
|
212
|
+
if raw_limit:
|
|
213
|
+
try:
|
|
214
|
+
max_chars = int(raw_limit)
|
|
215
|
+
except ValueError:
|
|
216
|
+
max_chars = 0
|
|
217
|
+
if max_chars <= 0:
|
|
218
|
+
return text
|
|
219
|
+
if len(text) <= max_chars:
|
|
220
|
+
return text
|
|
221
|
+
return text[:max_chars] + "\n...[truncated]"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def preflight_tool_check(tool_call: dict):
|
|
225
|
+
tool = tool_call.get("tool")
|
|
226
|
+
args = tool_call.get("args", {}) or {}
|
|
227
|
+
if tool == "git_cmd" and str(args.get("subcommand", "")).strip().lower() == "push":
|
|
228
|
+
status_result = run_tool(
|
|
229
|
+
"git_cmd",
|
|
230
|
+
{
|
|
231
|
+
"subcommand": "status",
|
|
232
|
+
"args": ["--porcelain"],
|
|
233
|
+
"cwd": args.get("cwd", "."),
|
|
234
|
+
},
|
|
235
|
+
)
|
|
236
|
+
if not status_result.get("ok"):
|
|
237
|
+
return {
|
|
238
|
+
"ok": False,
|
|
239
|
+
"error": "pre-push check failed: unable to verify working tree status",
|
|
240
|
+
"precheck": True,
|
|
241
|
+
}
|
|
242
|
+
if int(status_result.get("exit_code", 1)) != 0:
|
|
243
|
+
return {
|
|
244
|
+
"ok": False,
|
|
245
|
+
"error": "pre-push check failed: git status returned non-zero exit code",
|
|
246
|
+
"precheck": True,
|
|
247
|
+
}
|
|
248
|
+
if str(status_result.get("stdout", "")).strip():
|
|
249
|
+
return {
|
|
250
|
+
"ok": False,
|
|
251
|
+
"error": "push blocked: uncommitted changes detected. Commit or stash changes before pushing.",
|
|
252
|
+
"precheck": True,
|
|
253
|
+
}
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _git_is_mutating(tool_call: dict) -> bool:
|
|
258
|
+
if tool_call.get("tool") != "git_cmd":
|
|
259
|
+
return False
|
|
260
|
+
sub = str((tool_call.get("args") or {}).get("subcommand", "")).strip().lower()
|
|
261
|
+
return sub in {"add", "commit", "push", "pull", "switch", "checkout", "restore", "rm", "mv", "stash"}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def user_allows_mutations(user_text: str) -> bool:
|
|
265
|
+
t = (user_text or "").strip().lower()
|
|
266
|
+
if not t:
|
|
267
|
+
return False
|
|
268
|
+
if any(h in t for h in MUTATING_REQUEST_HINTS):
|
|
269
|
+
return True
|
|
270
|
+
if any(h in t for h in READ_ONLY_REQUEST_HINTS):
|
|
271
|
+
return False
|
|
272
|
+
if t.endswith("?"):
|
|
273
|
+
return False
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def tool_is_mutating(tool_call: dict) -> bool:
|
|
278
|
+
tool = tool_call.get("tool")
|
|
279
|
+
if tool in MUTATING_TOOLS:
|
|
280
|
+
if tool == "write_file":
|
|
281
|
+
return bool((tool_call.get("args") or {}).get("overwrite", False)) or True
|
|
282
|
+
return True
|
|
283
|
+
return _git_is_mutating(tool_call)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def should_enforce_readonly_guard(session: dict) -> bool:
|
|
287
|
+
provider = str(session.get("provider", "")).strip().lower()
|
|
288
|
+
model = str(tui.active_model(session)).strip().lower()
|
|
289
|
+
return provider == "groq" and "llama" in model
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def main():
|
|
293
|
+
system_prompt = build_system_prompt()
|
|
294
|
+
messages = [{"role": "system", "content": system_prompt}]
|
|
295
|
+
|
|
296
|
+
os.system("cls" if os.name == "nt" else "clear")
|
|
297
|
+
print(tui.c(BOOT_BANNER, tui.GREEN))
|
|
298
|
+
print(
|
|
299
|
+
tui.c("Has keys:", tui.GREEN),
|
|
300
|
+
f"groq={provider_has_key('groq')} openai={provider_has_key('openai')} anthropic={provider_has_key('anthropic')}",
|
|
301
|
+
)
|
|
302
|
+
print("Type 'exit' to quit.\n")
|
|
303
|
+
print("Use /help for provider/model commands.\n")
|
|
304
|
+
|
|
305
|
+
session = {
|
|
306
|
+
"provider": pick_startup_provider(),
|
|
307
|
+
"model_override": None,
|
|
308
|
+
"last_diff": None,
|
|
309
|
+
}
|
|
310
|
+
print(f"Active provider: {session['provider']} ({tui.active_model(session)})\n")
|
|
311
|
+
|
|
312
|
+
while True:
|
|
313
|
+
try:
|
|
314
|
+
user_text = tui.read_user_input(session)
|
|
315
|
+
except KeyboardInterrupt:
|
|
316
|
+
raise SystemExit(130)
|
|
317
|
+
if user_text is None:
|
|
318
|
+
continue
|
|
319
|
+
if not user_text.strip():
|
|
320
|
+
continue
|
|
321
|
+
if user_text.lower() in {"exit", "quit"}:
|
|
322
|
+
break
|
|
323
|
+
if tui.handle_ui_command(user_text, session):
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
allow_mutations = user_allows_mutations(user_text)
|
|
327
|
+
enforce_readonly_guard = should_enforce_readonly_guard(session)
|
|
328
|
+
messages.append({"role": "user", "content": user_text})
|
|
329
|
+
tool_index = 0
|
|
330
|
+
malformed_tool_retry_used = False
|
|
331
|
+
|
|
332
|
+
while True:
|
|
333
|
+
try:
|
|
334
|
+
indicator = tui.WorkingIndicator()
|
|
335
|
+
indicator.start()
|
|
336
|
+
try:
|
|
337
|
+
reply = call_provider(
|
|
338
|
+
provider=session["provider"],
|
|
339
|
+
messages=last_n_turns(messages, 6),
|
|
340
|
+
model=tui.active_model(session),
|
|
341
|
+
temperature=0.2,
|
|
342
|
+
)
|
|
343
|
+
finally:
|
|
344
|
+
indicator.stop()
|
|
345
|
+
except Exception as e:
|
|
346
|
+
print(f"▢ {tui.c('Baxter:', tui.RED)} model error: {e}")
|
|
347
|
+
break
|
|
348
|
+
|
|
349
|
+
messages.append({"role": "assistant", "content": reply})
|
|
350
|
+
tool_call = try_parse_tool_call(reply)
|
|
351
|
+
|
|
352
|
+
if not tool_call:
|
|
353
|
+
if not malformed_tool_retry_used and looks_like_broken_tool_call(reply):
|
|
354
|
+
malformed_tool_retry_used = True
|
|
355
|
+
messages.append(
|
|
356
|
+
{
|
|
357
|
+
"role": "user",
|
|
358
|
+
"content": (
|
|
359
|
+
"Your previous response looked like a tool call but was not valid JSON. "
|
|
360
|
+
"Respond again with exactly one valid JSON object on a single line in the "
|
|
361
|
+
'form {"tool":"<tool_name>","args":{...}} and no extra text.'
|
|
362
|
+
),
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
continue
|
|
366
|
+
tui.print_assistant_reply(reply)
|
|
367
|
+
break
|
|
368
|
+
|
|
369
|
+
tool_index += 1
|
|
370
|
+
tui.print_separator(f"Tool Step {tool_index}")
|
|
371
|
+
tui.print_tool_event(tool_call, tool_index)
|
|
372
|
+
|
|
373
|
+
if enforce_readonly_guard and (not allow_mutations) and tool_is_mutating(tool_call):
|
|
374
|
+
tool_result = {
|
|
375
|
+
"ok": False,
|
|
376
|
+
"error": "mutating tool blocked for read-only request",
|
|
377
|
+
"blocked": True,
|
|
378
|
+
"tool": tool_call.get("tool"),
|
|
379
|
+
}
|
|
380
|
+
tui.print_tool_result(tool_result, clip)
|
|
381
|
+
messages.append(
|
|
382
|
+
{
|
|
383
|
+
"role": "user",
|
|
384
|
+
"content": (
|
|
385
|
+
"TOOL_RESULT:\n"
|
|
386
|
+
f"{json.dumps(tool_result)}\n\n"
|
|
387
|
+
"The user's current request is read-only. "
|
|
388
|
+
"Do not call mutating tools. "
|
|
389
|
+
"Use read-only tools or answer directly."
|
|
390
|
+
),
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
continue
|
|
394
|
+
|
|
395
|
+
precheck_result = preflight_tool_check(tool_call)
|
|
396
|
+
if precheck_result is not None:
|
|
397
|
+
tool_result = precheck_result
|
|
398
|
+
else:
|
|
399
|
+
needs_confirm, confirm_prompt = tui.requires_confirmation(tool_call)
|
|
400
|
+
if needs_confirm and not tui.ask_confirmation(confirm_prompt, tool_call):
|
|
401
|
+
tool_result = {
|
|
402
|
+
"ok": False,
|
|
403
|
+
"error": "tool execution cancelled by user confirmation",
|
|
404
|
+
"cancelled": True,
|
|
405
|
+
"tool": tool_call.get("tool"),
|
|
406
|
+
}
|
|
407
|
+
tui.print_tool_result(tool_result, clip)
|
|
408
|
+
messages.append(
|
|
409
|
+
{
|
|
410
|
+
"role": "user",
|
|
411
|
+
"content": (
|
|
412
|
+
"TOOL_RESULT:\n"
|
|
413
|
+
f"{json.dumps(tool_result)}\n\n"
|
|
414
|
+
"The user denied this tool execution. "
|
|
415
|
+
"Do not retry the same mutating tool unless the user explicitly requests it. "
|
|
416
|
+
"Continue with safe read-only tools or provide a plain-English response."
|
|
417
|
+
),
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
print(tui.c("Tell Baxter what to do differently", tui.GREEN))
|
|
421
|
+
break
|
|
422
|
+
else:
|
|
423
|
+
tool_result = run_tool(tool_call["tool"], tool_call["args"])
|
|
424
|
+
if (
|
|
425
|
+
tool_call.get("tool") == "apply_diff"
|
|
426
|
+
and tool_result.get("ok")
|
|
427
|
+
and isinstance(tool_result.get("diff"), str)
|
|
428
|
+
):
|
|
429
|
+
session["last_diff"] = tool_result.get("diff")
|
|
430
|
+
|
|
431
|
+
tui.print_tool_result(tool_result, clip)
|
|
432
|
+
messages.append(
|
|
433
|
+
{
|
|
434
|
+
"role": "user",
|
|
435
|
+
"content": (
|
|
436
|
+
"TOOL_RESULT:\n"
|
|
437
|
+
f"{json.dumps(tool_result)}\n\n"
|
|
438
|
+
"You are still working on the user's current request. "
|
|
439
|
+
"If the request is not fully completed yet, call the next required tool now. "
|
|
440
|
+
"Do not stop after read/search/list tools when an edit was requested. "
|
|
441
|
+
"For git requests, execute git steps yourself with tools instead of asking the user to run commands. "
|
|
442
|
+
"Only claim edits when apply_diff/write_file/delete_path succeeded with ok=true. "
|
|
443
|
+
"If blocked, explain briefly and ask one concise follow-up."
|
|
444
|
+
),
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
if __name__ == "__main__":
|
|
450
|
+
main()
|