zrb 1.21.28__py3-none-any.whl → 1.21.31__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.
Potentially problematic release.
This version of zrb might be problematic. Click here for more details.
- zrb/builtin/llm/chat_completion.py +237 -224
- zrb/builtin/llm/chat_trigger.py +2 -3
- zrb/builtin/llm/tool/cli.py +25 -13
- zrb/builtin/llm/tool/code.py +5 -1
- zrb/builtin/llm/tool/file.py +6 -0
- zrb/builtin/llm/tool/note.py +7 -7
- zrb/builtin/llm/tool/search/__init__.py +1 -0
- zrb/builtin/llm/tool/search/brave.py +60 -0
- zrb/builtin/llm/tool/search/searxng.py +55 -0
- zrb/builtin/llm/tool/search/serpapi.py +55 -0
- zrb/builtin/llm/tool/web.py +12 -72
- zrb/task/llm/tool_confirmation_completer.py +41 -0
- zrb/task/llm/tool_wrapper.py +2 -3
- {zrb-1.21.28.dist-info → zrb-1.21.31.dist-info}/METADATA +1 -1
- {zrb-1.21.28.dist-info → zrb-1.21.31.dist-info}/RECORD +17 -12
- {zrb-1.21.28.dist-info → zrb-1.21.31.dist-info}/WHEEL +0 -0
- {zrb-1.21.28.dist-info → zrb-1.21.31.dist-info}/entry_points.txt +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
|
|
3
|
-
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
|
4
|
-
from prompt_toolkit.document import Document
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
5
3
|
|
|
6
4
|
from zrb.builtin.llm.chat_session_cmd import (
|
|
7
5
|
ADD_SUB_CMD,
|
|
@@ -36,239 +34,254 @@ from zrb.builtin.llm.chat_session_cmd import (
|
|
|
36
34
|
YOLO_SET_TRUE_CMD_DESC,
|
|
37
35
|
)
|
|
38
36
|
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from prompt_toolkit.completion import Completer
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_chat_completer() -> "Completer":
|
|
42
|
+
|
|
43
|
+
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
|
|
44
|
+
from prompt_toolkit.document import Document
|
|
39
45
|
|
|
40
|
-
class ChatCompleter(Completer):
|
|
46
|
+
class ChatCompleter(Completer):
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
def get_completions(self, document: Document, complete_event: CompleteEvent):
|
|
49
|
+
# Slash command
|
|
50
|
+
for completion in self._complete_slash_command(document):
|
|
51
|
+
yield completion
|
|
52
|
+
for completion in self._complete_slash_file_command(document):
|
|
53
|
+
yield completion
|
|
54
|
+
# Appendix
|
|
55
|
+
for completion in self._complete_appendix(document):
|
|
56
|
+
yield completion
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
def _complete_slash_file_command(self, document: Document):
|
|
59
|
+
text = document.text_before_cursor
|
|
60
|
+
prefixes = []
|
|
61
|
+
for cmd in ATTACHMENT_CMD:
|
|
62
|
+
for subcmd in ADD_SUB_CMD:
|
|
63
|
+
prefixes.append(f"{cmd} {subcmd} ")
|
|
64
|
+
for prefix in prefixes:
|
|
65
|
+
if text.startswith(prefix):
|
|
66
|
+
pattern = text[len(prefix) :]
|
|
67
|
+
potential_options = self._fuzzy_path_search(pattern, dirs=False)
|
|
68
|
+
for prefixed_option in [
|
|
69
|
+
f"{prefix}{option}" for option in potential_options
|
|
70
|
+
]:
|
|
71
|
+
yield Completion(
|
|
72
|
+
prefixed_option,
|
|
73
|
+
start_position=-len(text),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def _complete_slash_command(self, document: Document):
|
|
77
|
+
text = document.text_before_cursor
|
|
78
|
+
if not text.startswith("/"):
|
|
79
|
+
return
|
|
80
|
+
for command, description in self._get_cmd_options().items():
|
|
81
|
+
if command.lower().startswith(text.lower()):
|
|
65
82
|
yield Completion(
|
|
66
|
-
|
|
83
|
+
command,
|
|
67
84
|
start_position=-len(text),
|
|
85
|
+
display_meta=description,
|
|
68
86
|
)
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
def _complete_appendix(self, document: Document):
|
|
89
|
+
token = document.get_word_before_cursor(WORD=True)
|
|
90
|
+
prefix = "@"
|
|
91
|
+
if not token.startswith(prefix):
|
|
92
|
+
return
|
|
93
|
+
pattern = token[len(prefix) :]
|
|
94
|
+
potential_options = self._fuzzy_path_search(pattern, dirs=False)
|
|
95
|
+
for prefixed_option in [
|
|
96
|
+
f"{prefix}{option}" for option in potential_options
|
|
97
|
+
]:
|
|
76
98
|
yield Completion(
|
|
77
|
-
|
|
78
|
-
start_position=-len(
|
|
79
|
-
display_meta=description,
|
|
99
|
+
prefixed_option,
|
|
100
|
+
start_position=-len(token),
|
|
80
101
|
)
|
|
81
102
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
for subcmd in SET_SUB_CMD:
|
|
121
|
-
cmd_options[f"{cmd} {subcmd}"] = ATTACHMENT_SET_SUB_CMD_DESC
|
|
122
|
-
for cmd in YOLO_CMD:
|
|
123
|
-
cmd_options[cmd] = YOLO_CMD_DESC
|
|
124
|
-
for subcmd in SET_SUB_CMD:
|
|
125
|
-
cmd_options[f"{cmd} {subcmd} true"] = YOLO_SET_TRUE_CMD_DESC
|
|
126
|
-
cmd_options[f"{cmd} {subcmd} false"] = YOLO_SET_FALSE_CMD_DESC
|
|
127
|
-
cmd_options[f"{cmd} {subcmd}"] = YOLO_SET_CMD_DESC
|
|
128
|
-
for cmd in HELP_CMD:
|
|
129
|
-
cmd_options[cmd] = HELP_CMD_DESC
|
|
130
|
-
for cmd in RUN_CLI_CMD:
|
|
131
|
-
cmd_options[cmd] = RUN_CLI_CMD_DESC
|
|
132
|
-
return dict(sorted(cmd_options.items()))
|
|
103
|
+
def _get_cmd_options(self):
|
|
104
|
+
cmd_options = {}
|
|
105
|
+
# Add all commands with their descriptions
|
|
106
|
+
for cmd in MULTILINE_START_CMD:
|
|
107
|
+
cmd_options[cmd] = MULTILINE_START_CMD_DESC
|
|
108
|
+
for cmd in MULTILINE_END_CMD:
|
|
109
|
+
cmd_options[cmd] = MULTILINE_END_CMD_DESC
|
|
110
|
+
for cmd in QUIT_CMD:
|
|
111
|
+
cmd_options[cmd] = QUIT_CMD_DESC
|
|
112
|
+
for cmd in WORKFLOW_CMD:
|
|
113
|
+
cmd_options[cmd] = WORKFLOW_CMD_DESC
|
|
114
|
+
for subcmd in ADD_SUB_CMD:
|
|
115
|
+
cmd_options[f"{cmd} {subcmd}"] = WORKFLOW_ADD_SUB_CMD_DESC
|
|
116
|
+
for subcmd in CLEAR_SUB_CMD:
|
|
117
|
+
cmd_options[f"{cmd} {subcmd}"] = WORKFLOW_CLEAR_SUB_CMD_DESC
|
|
118
|
+
for subcmd in SET_SUB_CMD:
|
|
119
|
+
cmd_options[f"{cmd} {subcmd}"] = WORKFLOW_SET_SUB_CMD_DESC
|
|
120
|
+
for cmd in SAVE_CMD:
|
|
121
|
+
cmd_options[cmd] = SAVE_CMD_DESC
|
|
122
|
+
for cmd in ATTACHMENT_CMD:
|
|
123
|
+
cmd_options[cmd] = ATTACHMENT_CMD_DESC
|
|
124
|
+
for subcmd in ADD_SUB_CMD:
|
|
125
|
+
cmd_options[f"{cmd} {subcmd}"] = ATTACHMENT_ADD_SUB_CMD_DESC
|
|
126
|
+
for subcmd in CLEAR_SUB_CMD:
|
|
127
|
+
cmd_options[f"{cmd} {subcmd}"] = ATTACHMENT_CLEAR_SUB_CMD_DESC
|
|
128
|
+
for subcmd in SET_SUB_CMD:
|
|
129
|
+
cmd_options[f"{cmd} {subcmd}"] = ATTACHMENT_SET_SUB_CMD_DESC
|
|
130
|
+
for cmd in YOLO_CMD:
|
|
131
|
+
cmd_options[cmd] = YOLO_CMD_DESC
|
|
132
|
+
for subcmd in SET_SUB_CMD:
|
|
133
|
+
cmd_options[f"{cmd} {subcmd} true"] = YOLO_SET_TRUE_CMD_DESC
|
|
134
|
+
cmd_options[f"{cmd} {subcmd} false"] = YOLO_SET_FALSE_CMD_DESC
|
|
135
|
+
cmd_options[f"{cmd} {subcmd}"] = YOLO_SET_CMD_DESC
|
|
136
|
+
for cmd in HELP_CMD:
|
|
137
|
+
cmd_options[cmd] = HELP_CMD_DESC
|
|
138
|
+
for cmd in RUN_CLI_CMD:
|
|
139
|
+
cmd_options[cmd] = RUN_CLI_CMD_DESC
|
|
140
|
+
return dict(sorted(cmd_options.items()))
|
|
133
141
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
else:
|
|
161
|
-
root = os.path.dirname(expanded_pattern)
|
|
162
|
-
while root and not os.path.isdir(root) and len(root) > 1:
|
|
163
|
-
root = os.path.dirname(root)
|
|
164
|
-
if not os.path.isdir(root):
|
|
165
|
-
root = "." # Fallback
|
|
166
|
-
search_pattern = pattern
|
|
142
|
+
def _fuzzy_path_search(
|
|
143
|
+
self,
|
|
144
|
+
pattern: str,
|
|
145
|
+
root: str | None = None,
|
|
146
|
+
max_results: int = 20,
|
|
147
|
+
include_hidden: bool = False,
|
|
148
|
+
case_sensitive: bool = False,
|
|
149
|
+
dirs: bool = True,
|
|
150
|
+
files: bool = True,
|
|
151
|
+
) -> list[str]:
|
|
152
|
+
"""
|
|
153
|
+
Return a list of filesystem paths under `root` that fuzzy-match `pattern`.
|
|
154
|
+
- pattern: e.g. "./some/x" or "proj util/io"
|
|
155
|
+
- include_hidden: if False skip files/dirs starting with '.'
|
|
156
|
+
- dirs/files booleans let you restrict results
|
|
157
|
+
- returns list of relative paths (from root), sorted best-first
|
|
158
|
+
"""
|
|
159
|
+
search_pattern = pattern
|
|
160
|
+
if root is None:
|
|
161
|
+
# Determine root and adjust pattern if necessary
|
|
162
|
+
expanded_pattern = os.path.expanduser(pattern)
|
|
163
|
+
if os.path.isabs(expanded_pattern) or pattern.startswith("~"):
|
|
164
|
+
# For absolute paths, find the deepest existing directory
|
|
165
|
+
if os.path.isdir(expanded_pattern):
|
|
166
|
+
root = expanded_pattern
|
|
167
|
+
search_pattern = ""
|
|
167
168
|
else:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
search_pattern =
|
|
169
|
+
root = os.path.dirname(expanded_pattern)
|
|
170
|
+
while root and not os.path.isdir(root) and len(root) > 1:
|
|
171
|
+
root = os.path.dirname(root)
|
|
172
|
+
if not os.path.isdir(root):
|
|
173
|
+
root = "." # Fallback
|
|
174
|
+
search_pattern = pattern
|
|
175
|
+
else:
|
|
176
|
+
try:
|
|
177
|
+
search_pattern = os.path.relpath(expanded_pattern, root)
|
|
178
|
+
if search_pattern == ".":
|
|
179
|
+
search_pattern = ""
|
|
180
|
+
except ValueError:
|
|
181
|
+
search_pattern = os.path.basename(pattern)
|
|
182
|
+
else:
|
|
183
|
+
root = "."
|
|
184
|
+
search_pattern = pattern
|
|
185
|
+
# Normalize pattern -> tokens split on path separators or whitespace
|
|
186
|
+
search_pattern = search_pattern.strip()
|
|
187
|
+
if search_pattern:
|
|
188
|
+
raw_tokens = [t for t in search_pattern.split(os.path.sep) if t]
|
|
189
|
+
else:
|
|
190
|
+
raw_tokens = []
|
|
191
|
+
# prepare tokens (case)
|
|
192
|
+
if not case_sensitive:
|
|
193
|
+
tokens = [t.lower() for t in raw_tokens]
|
|
174
194
|
else:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
tokens = raw_tokens
|
|
188
|
-
# specific ignore list
|
|
189
|
-
try:
|
|
190
|
-
is_recursive = os.path.abspath(os.path.expanduser(root)).startswith(
|
|
191
|
-
os.path.abspath(os.getcwd())
|
|
192
|
-
)
|
|
193
|
-
except Exception:
|
|
194
|
-
is_recursive = False
|
|
195
|
-
# walk filesystem
|
|
196
|
-
candidates: list[tuple[float, str]] = []
|
|
197
|
-
for dirpath, dirnames, filenames in os.walk(root):
|
|
198
|
-
# Filter directories
|
|
199
|
-
if not include_hidden:
|
|
200
|
-
dirnames[:] = [d for d in dirnames if not d.startswith(".")]
|
|
201
|
-
rel_dir = os.path.relpath(dirpath, root)
|
|
202
|
-
# treat '.' as empty prefix
|
|
203
|
-
if rel_dir == ".":
|
|
204
|
-
rel_dir = ""
|
|
205
|
-
# build list of entries to test depending on files/dirs flags
|
|
206
|
-
entries = []
|
|
207
|
-
if dirs:
|
|
208
|
-
entries.extend([os.path.join(rel_dir, d) for d in dirnames])
|
|
209
|
-
if files:
|
|
210
|
-
entries.extend([os.path.join(rel_dir, f) for f in filenames])
|
|
211
|
-
if not is_recursive:
|
|
212
|
-
dirnames[:] = []
|
|
213
|
-
for ent in entries:
|
|
214
|
-
# Normalize presentation: use ./ prefix for relative paths
|
|
215
|
-
display_path = ent if ent else "."
|
|
216
|
-
# Skip hidden entries unless requested (double check for rel path segments)
|
|
195
|
+
tokens = raw_tokens
|
|
196
|
+
# specific ignore list
|
|
197
|
+
try:
|
|
198
|
+
is_recursive = os.path.abspath(os.path.expanduser(root)).startswith(
|
|
199
|
+
os.path.abspath(os.getcwd())
|
|
200
|
+
)
|
|
201
|
+
except Exception:
|
|
202
|
+
is_recursive = False
|
|
203
|
+
# walk filesystem
|
|
204
|
+
candidates: list[tuple[float, str]] = []
|
|
205
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
206
|
+
# Filter directories
|
|
217
207
|
if not include_hidden:
|
|
218
|
-
if
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
score
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
208
|
+
dirnames[:] = [d for d in dirnames if not d.startswith(".")]
|
|
209
|
+
rel_dir = os.path.relpath(dirpath, root)
|
|
210
|
+
# treat '.' as empty prefix
|
|
211
|
+
if rel_dir == ".":
|
|
212
|
+
rel_dir = ""
|
|
213
|
+
# build list of entries to test depending on files/dirs flags
|
|
214
|
+
entries = []
|
|
215
|
+
if dirs:
|
|
216
|
+
entries.extend([os.path.join(rel_dir, d) for d in dirnames])
|
|
217
|
+
if files:
|
|
218
|
+
entries.extend([os.path.join(rel_dir, f) for f in filenames])
|
|
219
|
+
if not is_recursive:
|
|
220
|
+
dirnames[:] = []
|
|
221
|
+
for ent in entries:
|
|
222
|
+
# Normalize presentation: use ./ prefix for relative paths
|
|
223
|
+
display_path = ent if ent else "."
|
|
224
|
+
# Skip hidden entries unless requested (double check for rel path segments)
|
|
225
|
+
if not include_hidden:
|
|
226
|
+
if any(
|
|
227
|
+
seg.startswith(".")
|
|
228
|
+
for seg in display_path.split(os.sep)
|
|
229
|
+
if seg
|
|
230
|
+
):
|
|
231
|
+
continue
|
|
232
|
+
cand = display_path.replace(os.sep, "/") # unify separator
|
|
233
|
+
cand_cmp = cand if case_sensitive else cand.lower()
|
|
234
|
+
last_pos = 0
|
|
235
|
+
score = 0.0
|
|
236
|
+
matched_all = True
|
|
237
|
+
for token in tokens:
|
|
238
|
+
# try contiguous substring search first
|
|
239
|
+
idx = cand_cmp.find(token, last_pos)
|
|
240
|
+
if idx != -1:
|
|
241
|
+
# good match: reward contiguous early matches
|
|
242
|
+
score += idx # smaller idx preferred
|
|
243
|
+
last_pos = idx + len(token)
|
|
244
|
+
else:
|
|
245
|
+
# fallback to subsequence matching
|
|
246
|
+
pos = self._find_subsequence_pos(cand_cmp, token, last_pos)
|
|
247
|
+
if pos is None:
|
|
248
|
+
matched_all = False
|
|
249
|
+
break
|
|
250
|
+
# subsequence match is less preferred than contiguous substring
|
|
251
|
+
score += pos + 0.5 * len(token)
|
|
252
|
+
last_pos = pos + len(token)
|
|
253
|
+
if matched_all:
|
|
254
|
+
# prefer shorter paths when score ties, so include length as tiebreaker
|
|
255
|
+
score += 0.01 * len(cand)
|
|
256
|
+
out = (
|
|
257
|
+
cand
|
|
258
|
+
if os.path.abspath(cand) == cand
|
|
259
|
+
else os.path.join(root, cand)
|
|
260
|
+
)
|
|
261
|
+
candidates.append((score, out))
|
|
262
|
+
# sort by score then lexicographically and return top results
|
|
263
|
+
candidates.sort(key=lambda x: (x[0], x[1]))
|
|
264
|
+
return [p for _, p in candidates[:max_results]]
|
|
265
|
+
|
|
266
|
+
def _find_subsequence_pos(
|
|
267
|
+
self, hay: str, needle: str, start: int = 0
|
|
268
|
+
) -> int | None:
|
|
269
|
+
"""
|
|
270
|
+
Try to locate needle in hay as a subsequence starting at `start`.
|
|
271
|
+
Returns the index of the first matched character of the subsequence or None if not
|
|
272
|
+
match.
|
|
273
|
+
"""
|
|
274
|
+
if not needle:
|
|
275
|
+
return start
|
|
276
|
+
i = start
|
|
277
|
+
j = 0
|
|
278
|
+
first_pos = None
|
|
279
|
+
while i < len(hay) and j < len(needle):
|
|
280
|
+
if hay[i] == needle[j]:
|
|
281
|
+
if first_pos is None:
|
|
282
|
+
first_pos = i
|
|
283
|
+
j += 1
|
|
284
|
+
i += 1
|
|
285
|
+
return first_pos if j == len(needle) else None
|
|
255
286
|
|
|
256
|
-
|
|
257
|
-
self, hay: str, needle: str, start: int = 0
|
|
258
|
-
) -> int | None:
|
|
259
|
-
"""
|
|
260
|
-
Try to locate needle in hay as a subsequence starting at `start`.
|
|
261
|
-
Returns the index of the first matched character of the subsequence or None if not match.
|
|
262
|
-
"""
|
|
263
|
-
if not needle:
|
|
264
|
-
return start
|
|
265
|
-
i = start
|
|
266
|
-
j = 0
|
|
267
|
-
first_pos = None
|
|
268
|
-
while i < len(hay) and j < len(needle):
|
|
269
|
-
if hay[i] == needle[j]:
|
|
270
|
-
if first_pos is None:
|
|
271
|
-
first_pos = i
|
|
272
|
-
j += 1
|
|
273
|
-
i += 1
|
|
274
|
-
return first_pos if j == len(needle) else None
|
|
287
|
+
return ChatCompleter()
|
zrb/builtin/llm/chat_trigger.py
CHANGED
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
from asyncio import StreamReader
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Callable, Coroutine
|
|
5
5
|
|
|
6
|
+
from zrb.builtin.llm.chat_completion import get_chat_completer
|
|
6
7
|
from zrb.context.any_context import AnyContext
|
|
7
8
|
from zrb.util.run import run_async
|
|
8
9
|
|
|
@@ -57,13 +58,11 @@ class LLMChatTrigger:
|
|
|
57
58
|
"""Reads one line of input using the provided reader."""
|
|
58
59
|
from prompt_toolkit import PromptSession
|
|
59
60
|
|
|
60
|
-
from zrb.builtin.llm.chat_completion import ChatCompleter
|
|
61
|
-
|
|
62
61
|
try:
|
|
63
62
|
if isinstance(reader, PromptSession):
|
|
64
63
|
bottom_toolbar = f"📁 Current directory: {os.getcwd()}"
|
|
65
64
|
return await reader.prompt_async(
|
|
66
|
-
completer=
|
|
65
|
+
completer=get_chat_completer(), bottom_toolbar=bottom_toolbar
|
|
67
66
|
)
|
|
68
67
|
line_bytes = await reader.readline()
|
|
69
68
|
if not line_bytes:
|
zrb/builtin/llm/tool/cli.py
CHANGED
|
@@ -22,7 +22,7 @@ class ShellCommandResult(TypedDict):
|
|
|
22
22
|
stderr: str
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def run_shell_command(command: str) -> ShellCommandResult:
|
|
25
|
+
def run_shell_command(command: str, timeout: int = 30) -> ShellCommandResult:
|
|
26
26
|
"""
|
|
27
27
|
Executes a non-interactive shell command on the user's machine.
|
|
28
28
|
|
|
@@ -31,22 +31,34 @@ def run_shell_command(command: str) -> ShellCommandResult:
|
|
|
31
31
|
IMPORTANT: Long-running processes should be run in the background (e.g., `command &`).
|
|
32
32
|
|
|
33
33
|
Example:
|
|
34
|
-
run_shell_command(command='ls -l')
|
|
34
|
+
run_shell_command(command='ls -l', timeout=30)
|
|
35
35
|
|
|
36
36
|
Args:
|
|
37
37
|
command (str): The exact shell command to be executed.
|
|
38
|
+
timeout (int): The maximum time in seconds to wait for the command to finish.
|
|
39
|
+
Defaults to 30.
|
|
38
40
|
|
|
39
41
|
Returns:
|
|
40
42
|
dict: return_code, stdout, and stderr.
|
|
41
43
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
try:
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
command,
|
|
47
|
+
shell=True,
|
|
48
|
+
capture_output=True,
|
|
49
|
+
text=True,
|
|
50
|
+
timeout=timeout,
|
|
51
|
+
)
|
|
52
|
+
return {
|
|
53
|
+
"return_code": int(result.returncode),
|
|
54
|
+
"stdout": str(result.stdout or ""),
|
|
55
|
+
"stderr": str(result.stderr or ""),
|
|
56
|
+
}
|
|
57
|
+
except subprocess.TimeoutExpired as e:
|
|
58
|
+
stdout = e.stdout.decode() if isinstance(e.stdout, bytes) else (e.stdout or "")
|
|
59
|
+
stderr = e.stderr.decode() if isinstance(e.stderr, bytes) else (e.stderr or "")
|
|
60
|
+
return {
|
|
61
|
+
"return_code": 124,
|
|
62
|
+
"stdout": str(stdout),
|
|
63
|
+
"stderr": f"{stderr}\nError: Command timed out after {timeout} seconds".strip(),
|
|
64
|
+
}
|
zrb/builtin/llm/tool/code.py
CHANGED
|
@@ -59,8 +59,12 @@ async def analyze_repo(
|
|
|
59
59
|
"""
|
|
60
60
|
Analyzes a code repository or directory to answer a specific query.
|
|
61
61
|
|
|
62
|
-
CRITICAL: The
|
|
62
|
+
CRITICAL: The query must contain ALL necessary context, instructions, and information.
|
|
63
|
+
The sub-agent performing the analysis does NOT share your current conversation
|
|
64
|
+
history, memory, or global context.
|
|
65
|
+
The quality of analysis depends entirely on the query. Vague queries yield poor
|
|
63
66
|
results.
|
|
67
|
+
|
|
64
68
|
IMPORTANT: This tool can be slow and expensive on large repositories. Use judiciously.
|
|
65
69
|
|
|
66
70
|
Example:
|
zrb/builtin/llm/tool/file.py
CHANGED
|
@@ -516,6 +516,12 @@ async def analyze_file(
|
|
|
516
516
|
"""
|
|
517
517
|
Analyzes a file using a sub-agent for complex questions.
|
|
518
518
|
|
|
519
|
+
CRITICAL: The query must contain ALL necessary context, instructions, and information.
|
|
520
|
+
The sub-agent performing the analysis does NOT share your current conversation
|
|
521
|
+
history, memory, or global context.
|
|
522
|
+
The quality of analysis depends entirely on the query. Vague queries yield poor
|
|
523
|
+
results.
|
|
524
|
+
|
|
519
525
|
Example:
|
|
520
526
|
analyze_file(path='src/main.py', query='Summarize the main function.')
|
|
521
527
|
|
zrb/builtin/llm/tool/note.py
CHANGED
|
@@ -5,9 +5,9 @@ from zrb.config.llm_context.config import llm_context_config
|
|
|
5
5
|
|
|
6
6
|
def read_long_term_note() -> str:
|
|
7
7
|
"""
|
|
8
|
-
Retrieves the GLOBAL
|
|
8
|
+
Retrieves the GLOBAL 🧠 Long Term Note shared across ALL sessions and projects.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Use this to recall user preferences, facts, and cross-project context.
|
|
11
11
|
|
|
12
12
|
Returns:
|
|
13
13
|
str: The current global note content.
|
|
@@ -18,7 +18,7 @@ def read_long_term_note() -> str:
|
|
|
18
18
|
|
|
19
19
|
def write_long_term_note(content: str) -> str:
|
|
20
20
|
"""
|
|
21
|
-
Persists CRITICAL facts to the GLOBAL
|
|
21
|
+
Persists CRITICAL facts to the GLOBAL 🧠 Long Term Note.
|
|
22
22
|
|
|
23
23
|
USE EAGERLY to save or update:
|
|
24
24
|
- User preferences (e.g., "I prefer Python", "No unit tests").
|
|
@@ -27,7 +27,7 @@ def write_long_term_note(content: str) -> str:
|
|
|
27
27
|
- Cross-project goals.
|
|
28
28
|
- Anything that will be useful for future interaction across projects.
|
|
29
29
|
|
|
30
|
-
WARNING: This OVERWRITES the entire
|
|
30
|
+
WARNING: This OVERWRITES the entire Long Term Note.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
33
|
content (str): The text to strictly memorize.
|
|
@@ -41,7 +41,7 @@ def write_long_term_note(content: str) -> str:
|
|
|
41
41
|
|
|
42
42
|
def read_contextual_note(path: str | None = None) -> str:
|
|
43
43
|
"""
|
|
44
|
-
Retrieves LOCAL
|
|
44
|
+
Retrieves LOCAL 📝 Contextual Note specific to a directory path.
|
|
45
45
|
|
|
46
46
|
Use to recall project-specific architecture, code summaries, or past decisions
|
|
47
47
|
relevant to the current working location.
|
|
@@ -61,7 +61,7 @@ def read_contextual_note(path: str | None = None) -> str:
|
|
|
61
61
|
|
|
62
62
|
def write_contextual_note(content: str, path: str | None = None) -> str:
|
|
63
63
|
"""
|
|
64
|
-
Persists LOCAL facts specific to a
|
|
64
|
+
Persists LOCAL facts specific to a directory into 📝 Contextual Note.
|
|
65
65
|
|
|
66
66
|
USE EAGERLY to save or update:
|
|
67
67
|
- Architectural patterns for this project/directory.
|
|
@@ -69,7 +69,7 @@ def write_contextual_note(content: str, path: str | None = None) -> str:
|
|
|
69
69
|
- Specific guidelines for this project.
|
|
70
70
|
- Anything related to this directory that will be useful for future interaction.
|
|
71
71
|
|
|
72
|
-
WARNING: This OVERWRITES the
|
|
72
|
+
WARNING: This OVERWRITES the entire Contextual Note for a directory.
|
|
73
73
|
|
|
74
74
|
Args:
|
|
75
75
|
content (str): The text to memorize for this location.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file makes the directory a Python package
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_internet(
|
|
9
|
+
query: str,
|
|
10
|
+
page: int = 1,
|
|
11
|
+
safe_search: str | None = None,
|
|
12
|
+
language: str | None = None,
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Performs an internet search using Brave Search.
|
|
16
|
+
|
|
17
|
+
Use this tool to find up-to-date information, answer questions about current events,
|
|
18
|
+
or research topics using a search engine.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
query (str): The natural language search query (e.g., 'Soto Madura').
|
|
22
|
+
Do NOT include instructions, meta-talk, or internal reasoning.
|
|
23
|
+
Use concise terms as a human would in a search engine.
|
|
24
|
+
page (int): Search result page number. Defaults to 1.
|
|
25
|
+
safe_search (str | None): Safety setting. 'strict', 'moderate', or 'off'.
|
|
26
|
+
If None, uses the system default configuration.
|
|
27
|
+
language (str | None): Language code (e.g., 'en').
|
|
28
|
+
If None, uses the system default configuration.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
dict: Summary of search results (titles, links, snippets).
|
|
32
|
+
"""
|
|
33
|
+
if safe_search is None:
|
|
34
|
+
safe_search = CFG.BRAVE_API_SAFE
|
|
35
|
+
if language is None:
|
|
36
|
+
language = CFG.BRAVE_API_LANG
|
|
37
|
+
|
|
38
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa
|
|
39
|
+
|
|
40
|
+
response = requests.get(
|
|
41
|
+
"https://api.search.brave.com/res/v1/web/search",
|
|
42
|
+
headers={
|
|
43
|
+
"User-Agent": user_agent,
|
|
44
|
+
"Accept": "application/json",
|
|
45
|
+
"x-subscription-token": CFG.BRAVE_API_KEY,
|
|
46
|
+
},
|
|
47
|
+
params={
|
|
48
|
+
"q": query,
|
|
49
|
+
"count": "10",
|
|
50
|
+
"offset": (page - 1) * 10,
|
|
51
|
+
"safesearch": safe_search,
|
|
52
|
+
"search_lang": language,
|
|
53
|
+
"summary": "true",
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
if response.status_code != 200:
|
|
57
|
+
raise Exception(
|
|
58
|
+
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
|
59
|
+
)
|
|
60
|
+
return response.json()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_internet(
|
|
9
|
+
query: str,
|
|
10
|
+
page: int = 1,
|
|
11
|
+
safe_search: int | None = None,
|
|
12
|
+
language: str | None = None,
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Performs an internet search using SearXNG.
|
|
16
|
+
|
|
17
|
+
Use this tool to find up-to-date information, answer questions about current events,
|
|
18
|
+
or research topics using a search engine.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
query (str): The natural language search query (e.g., 'Soto Madura').
|
|
22
|
+
Do NOT include instructions, meta-talk, or internal reasoning.
|
|
23
|
+
Use concise terms as a human would in a search engine.
|
|
24
|
+
page (int): Search result page number. Defaults to 1.
|
|
25
|
+
safe_search (int | None): Safety setting. 0 (None), 1 (Moderate), 2 (Strict).
|
|
26
|
+
If None, uses the system default configuration.
|
|
27
|
+
language (str | None): Language code (e.g., 'en').
|
|
28
|
+
If None, uses the system default configuration.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
dict: Summary of search results (titles, links, snippets).
|
|
32
|
+
"""
|
|
33
|
+
if safe_search is None:
|
|
34
|
+
safe_search = CFG.SEARXNG_SAFE
|
|
35
|
+
if language is None:
|
|
36
|
+
language = CFG.SEARXNG_LANG
|
|
37
|
+
|
|
38
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa
|
|
39
|
+
|
|
40
|
+
response = requests.get(
|
|
41
|
+
url=f"{CFG.SEARXNG_BASE_URL}/search",
|
|
42
|
+
headers={"User-Agent": user_agent},
|
|
43
|
+
params={
|
|
44
|
+
"q": query,
|
|
45
|
+
"format": "json",
|
|
46
|
+
"pageno": page,
|
|
47
|
+
"safesearch": safe_search,
|
|
48
|
+
"language": language,
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
if response.status_code != 200:
|
|
52
|
+
raise Exception(
|
|
53
|
+
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
|
54
|
+
)
|
|
55
|
+
return response.json()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from zrb.config.config import CFG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_internet(
|
|
9
|
+
query: str,
|
|
10
|
+
page: int = 1,
|
|
11
|
+
safe_search: str | None = None,
|
|
12
|
+
language: str | None = None,
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""
|
|
15
|
+
Performs an internet search using SerpApi (Google).
|
|
16
|
+
|
|
17
|
+
Use this tool to find up-to-date information, answer questions about current events,
|
|
18
|
+
or research topics using a search engine.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
query (str): The natural language search query (e.g., 'Soto Madura').
|
|
22
|
+
Do NOT include instructions, meta-talk, or internal reasoning.
|
|
23
|
+
Use concise terms as a human would in a search engine.
|
|
24
|
+
page (int): Search result page number. Defaults to 1.
|
|
25
|
+
safe_search (str | None): Safety setting. 'active' or 'off'.
|
|
26
|
+
If None, uses the system default configuration.
|
|
27
|
+
language (str | None): Two-letter language code (e.g., 'en', 'id').
|
|
28
|
+
If None, uses the system default configuration.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
dict: Summary of search results (titles, links, snippets).
|
|
32
|
+
"""
|
|
33
|
+
if safe_search is None:
|
|
34
|
+
safe_search = CFG.SERPAPI_SAFE
|
|
35
|
+
if language is None:
|
|
36
|
+
language = CFG.SERPAPI_LANG
|
|
37
|
+
|
|
38
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa
|
|
39
|
+
|
|
40
|
+
response = requests.get(
|
|
41
|
+
"https://serpapi.com/search",
|
|
42
|
+
headers={"User-Agent": user_agent},
|
|
43
|
+
params={
|
|
44
|
+
"q": query,
|
|
45
|
+
"start": (page - 1) * 10,
|
|
46
|
+
"hl": language,
|
|
47
|
+
"safe": safe_search,
|
|
48
|
+
"api_key": CFG.SERPAPI_KEY,
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
if response.status_code != 200:
|
|
52
|
+
raise Exception(
|
|
53
|
+
f"Error: Unable to retrieve search results (status code: {response.status_code})"
|
|
54
|
+
)
|
|
55
|
+
return response.json()
|
zrb/builtin/llm/tool/web.py
CHANGED
|
@@ -2,6 +2,7 @@ from collections.abc import Callable
|
|
|
2
2
|
from typing import Any
|
|
3
3
|
from urllib.parse import urljoin
|
|
4
4
|
|
|
5
|
+
from zrb.builtin.llm.tool.search import brave, searxng, serpapi
|
|
5
6
|
from zrb.config.config import CFG
|
|
6
7
|
from zrb.config.llm_config import llm_config
|
|
7
8
|
|
|
@@ -30,78 +31,17 @@ async def open_web_page(url: str) -> dict[str, Any]:
|
|
|
30
31
|
def create_search_internet_tool() -> Callable:
|
|
31
32
|
if llm_config.default_search_internet_tool is not None:
|
|
32
33
|
return llm_config.default_search_internet_tool
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
""
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
page (int, optional): Search result page number. Defaults to 1.
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
dict: Summary of search results (titles, links, snippets).
|
|
48
|
-
"""
|
|
49
|
-
import requests
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
CFG.SEARCH_INTERNET_METHOD.strip().lower() == "serpapi"
|
|
53
|
-
and CFG.SERPAPI_KEY != ""
|
|
54
|
-
):
|
|
55
|
-
response = requests.get(
|
|
56
|
-
"https://serpapi.com/search",
|
|
57
|
-
headers={"User-Agent": _DEFAULT_USER_AGENT},
|
|
58
|
-
params={
|
|
59
|
-
"q": query,
|
|
60
|
-
"start": (page - 1) * 10,
|
|
61
|
-
"hl": CFG.SERPAPI_LANG,
|
|
62
|
-
"safe": CFG.SERPAPI_SAFE,
|
|
63
|
-
"api_key": CFG.SERPAPI_KEY,
|
|
64
|
-
},
|
|
65
|
-
)
|
|
66
|
-
elif (
|
|
67
|
-
CFG.SEARCH_INTERNET_METHOD.strip().lower() == "brave"
|
|
68
|
-
and CFG.BRAVE_API_KEY != ""
|
|
69
|
-
):
|
|
70
|
-
response = requests.get(
|
|
71
|
-
"https://api.search.brave.com/res/v1/web/search",
|
|
72
|
-
headers={
|
|
73
|
-
"User-Agent": _DEFAULT_USER_AGENT,
|
|
74
|
-
"Accept": "application/json",
|
|
75
|
-
"x-subscription-token": CFG.BRAVE_API_KEY,
|
|
76
|
-
},
|
|
77
|
-
params={
|
|
78
|
-
"q": query,
|
|
79
|
-
"count": "10",
|
|
80
|
-
"offset": (page - 1) * 10,
|
|
81
|
-
"safesearch": CFG.BRAVE_API_SAFE,
|
|
82
|
-
"search_lang": CFG.BRAVE_API_LANG,
|
|
83
|
-
"summary": "true",
|
|
84
|
-
},
|
|
85
|
-
)
|
|
86
|
-
else:
|
|
87
|
-
response = requests.get(
|
|
88
|
-
url=f"{CFG.SEARXNG_BASE_URL}/search",
|
|
89
|
-
headers={"User-Agent": _DEFAULT_USER_AGENT},
|
|
90
|
-
params={
|
|
91
|
-
"q": query,
|
|
92
|
-
"format": "json",
|
|
93
|
-
"pageno": page,
|
|
94
|
-
"safesearch": CFG.SEARXNG_SAFE,
|
|
95
|
-
"language": CFG.SEARXNG_LANG,
|
|
96
|
-
},
|
|
97
|
-
)
|
|
98
|
-
if response.status_code != 200:
|
|
99
|
-
raise Exception(
|
|
100
|
-
f"Error: Unable to retrieve search results (status code: {response.status_code})" # noqa
|
|
101
|
-
)
|
|
102
|
-
return response.json()
|
|
103
|
-
|
|
104
|
-
return search_internet
|
|
34
|
+
if (
|
|
35
|
+
CFG.SEARCH_INTERNET_METHOD.strip().lower() == "serpapi"
|
|
36
|
+
and CFG.SERPAPI_KEY != ""
|
|
37
|
+
):
|
|
38
|
+
return serpapi.search_internet
|
|
39
|
+
if (
|
|
40
|
+
CFG.SEARCH_INTERNET_METHOD.strip().lower() == "brave"
|
|
41
|
+
and CFG.BRAVE_API_KEY != ""
|
|
42
|
+
):
|
|
43
|
+
return brave.search_internet
|
|
44
|
+
return searxng.search_internet
|
|
105
45
|
|
|
106
46
|
|
|
107
47
|
async def _fetch_page_content(url: str) -> tuple[str, list[str]]:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from prompt_toolkit.completion import Completer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_tool_confirmation_completer(
|
|
8
|
+
options: list[str], meta_dict: dict[str, str]
|
|
9
|
+
) -> "Completer":
|
|
10
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
11
|
+
|
|
12
|
+
class ToolConfirmationCompleter(Completer):
|
|
13
|
+
"""Custom completer for tool confirmation that doesn't auto-complete partial words."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, options, meta_dict):
|
|
16
|
+
self.options = options
|
|
17
|
+
self.meta_dict = meta_dict
|
|
18
|
+
|
|
19
|
+
def get_completions(self, document, complete_event):
|
|
20
|
+
text = document.text.strip()
|
|
21
|
+
# 1. Input is empty, OR
|
|
22
|
+
# 2. Input exactly matches the beginning of an option
|
|
23
|
+
if text == "":
|
|
24
|
+
# Show all options when nothing is typed
|
|
25
|
+
for option in self.options:
|
|
26
|
+
yield Completion(
|
|
27
|
+
option,
|
|
28
|
+
start_position=0,
|
|
29
|
+
display_meta=self.meta_dict.get(option, ""),
|
|
30
|
+
)
|
|
31
|
+
return
|
|
32
|
+
# Only complete if text exactly matches the beginning of an option
|
|
33
|
+
for option in self.options:
|
|
34
|
+
if option.startswith(text):
|
|
35
|
+
yield Completion(
|
|
36
|
+
option,
|
|
37
|
+
start_position=-len(text),
|
|
38
|
+
display_meta=self.meta_dict.get(option, ""),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return ToolConfirmationCompleter(options, meta_dict)
|
zrb/task/llm/tool_wrapper.py
CHANGED
|
@@ -11,11 +11,11 @@ from zrb.config.llm_rate_limitter import llm_rate_limitter
|
|
|
11
11
|
from zrb.context.any_context import AnyContext
|
|
12
12
|
from zrb.task.llm.error import ToolExecutionError
|
|
13
13
|
from zrb.task.llm.file_replacement import edit_replacement, is_single_path_replacement
|
|
14
|
+
from zrb.task.llm.tool_confirmation_completer import get_tool_confirmation_completer
|
|
14
15
|
from zrb.util.callable import get_callable_name
|
|
15
16
|
from zrb.util.cli.markdown import render_markdown
|
|
16
17
|
from zrb.util.cli.style import (
|
|
17
18
|
stylize_blue,
|
|
18
|
-
stylize_error,
|
|
19
19
|
stylize_faint,
|
|
20
20
|
stylize_green,
|
|
21
21
|
stylize_yellow,
|
|
@@ -296,7 +296,6 @@ def _truncate_arg(arg: str, length: int = 19) -> str:
|
|
|
296
296
|
|
|
297
297
|
async def _read_line(args: list[Any] | tuple[Any], kwargs: dict[str, Any]):
|
|
298
298
|
from prompt_toolkit import PromptSession
|
|
299
|
-
from prompt_toolkit.completion import WordCompleter
|
|
300
299
|
|
|
301
300
|
options = ["yes", "no", "edit"]
|
|
302
301
|
meta_dict = {
|
|
@@ -307,7 +306,7 @@ async def _read_line(args: list[Any] | tuple[Any], kwargs: dict[str, Any]):
|
|
|
307
306
|
for key in kwargs:
|
|
308
307
|
options.append(f"edit {key}")
|
|
309
308
|
meta_dict[f"edit {key}"] = f"Edit tool execution parameter: {key}"
|
|
310
|
-
completer =
|
|
309
|
+
completer = get_tool_confirmation_completer(options, meta_dict)
|
|
311
310
|
reader = PromptSession()
|
|
312
311
|
return await reader.prompt_async(completer=completer)
|
|
313
312
|
|
|
@@ -10,23 +10,27 @@ zrb/builtin/group.py,sha256=W_IA_4414c8Wi8QazqcT6Gq6UftrKwy95yMF114EJtI,2677
|
|
|
10
10
|
zrb/builtin/http.py,sha256=L6RE73c65wWwG5iHFN-tpOhyh56KsrgVskDd3c3YXtk,4246
|
|
11
11
|
zrb/builtin/jwt.py,sha256=3M5uaQhJZbKQLjTUft1OwPz_JxtmK-xtkjxWjciOQho,2859
|
|
12
12
|
zrb/builtin/llm/attachment.py,sha256=UwhOGtZY9Px9aebsKfTTw2_ZIpbsqx0XgoAonk6Ewkc,1344
|
|
13
|
-
zrb/builtin/llm/chat_completion.py,sha256=
|
|
13
|
+
zrb/builtin/llm/chat_completion.py,sha256=cWVju5ApC2OPGM3ZWGaXwSgLwgT9leKS_TbV4z610zc,12252
|
|
14
14
|
zrb/builtin/llm/chat_session.py,sha256=TmXL7IkVT22blgWqG625qpr2_BnDMIm7ERYbR61BL4Q,9154
|
|
15
15
|
zrb/builtin/llm/chat_session_cmd.py,sha256=lGiN-YNtv957wYuuF_mXV0opbRN61U9pyxVVq-_Gwv8,10842
|
|
16
|
-
zrb/builtin/llm/chat_trigger.py,sha256=
|
|
16
|
+
zrb/builtin/llm/chat_trigger.py,sha256=i6_lej2xYfY8k5-CMIUsdYQdUvBV9ma_DM6ZmgLaQIU,2673
|
|
17
17
|
zrb/builtin/llm/history.py,sha256=qNRYq6SlJH5iuzdra_FOJxevWXCDh5YMvN2qPNLjI8I,3099
|
|
18
18
|
zrb/builtin/llm/input.py,sha256=Nw-26uTWp2QhUgKJcP_IMHmtk-b542CCSQ_vCOjhvhM,877
|
|
19
19
|
zrb/builtin/llm/llm_ask.py,sha256=6fUbCFjgjFiTVf-88WJZ1RD5wgOzmsVEpD3dR16Jl_8,8598
|
|
20
20
|
zrb/builtin/llm/previous-session.js,sha256=xMKZvJoAbrwiyHS0OoPrWuaKxWYLoyR5sguePIoCjTY,816
|
|
21
21
|
zrb/builtin/llm/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
zrb/builtin/llm/tool/api.py,sha256=ItGJLr_1vhmLcitfIKeVDrnJEWdsyqucDnQ7enIC8fQ,2394
|
|
23
|
-
zrb/builtin/llm/tool/cli.py,sha256=
|
|
24
|
-
zrb/builtin/llm/tool/code.py,sha256=
|
|
25
|
-
zrb/builtin/llm/tool/file.py,sha256=
|
|
26
|
-
zrb/builtin/llm/tool/note.py,sha256=
|
|
23
|
+
zrb/builtin/llm/tool/cli.py,sha256=eNmNgNR5H57idYAIXJ50mipAs2KzYGiN44h7xFHvF-k,1917
|
|
24
|
+
zrb/builtin/llm/tool/code.py,sha256=1sErE5tsticXGNhiE0bTjI21Zq6zHMgS2hVM6dsAcww,7916
|
|
25
|
+
zrb/builtin/llm/tool/file.py,sha256=LLdkE_hTZYDm9x3EwAo9Xsp7T_OV6ljmGLJksr-e4CM,19622
|
|
26
|
+
zrb/builtin/llm/tool/note.py,sha256=USDQe8F1ddQp5dhv2u35rUN9EfJQl5BaWJIs6Xg9ZZY,2597
|
|
27
27
|
zrb/builtin/llm/tool/rag.py,sha256=lmoA42IWHcBOWTsWzYGklLs1YoiEldvu_Bi5bp6gbts,9439
|
|
28
|
+
zrb/builtin/llm/tool/search/__init__.py,sha256=wiMaie_IYt00fak-LrioC1qEZDs0SwYz4UWwidHfpFU,49
|
|
29
|
+
zrb/builtin/llm/tool/search/brave.py,sha256=WDRki3UZ9KnzZ15QxCube6LcgnC8G1bTTv-MWfowwe8,2013
|
|
30
|
+
zrb/builtin/llm/tool/search/searxng.py,sha256=0XzcegO1cO3r926z0O8ostD-R-8ysLQCJXLB4RTQ0J0,1834
|
|
31
|
+
zrb/builtin/llm/tool/search/serpapi.py,sha256=UxRzLqKZGcNhvOzj6CRWQFgtYTN3OCWpyT9w9OKlytc,1843
|
|
28
32
|
zrb/builtin/llm/tool/sub_agent.py,sha256=UNgsvZvSQ1b5ybB0nRzTiFCJpZG3g2RLj_nzjRZzXMY,5808
|
|
29
|
-
zrb/builtin/llm/tool/web.py,sha256=
|
|
33
|
+
zrb/builtin/llm/tool/web.py,sha256=xtfbGNEW9KYfTg-mTp0wpVziQ5NFi6rslNzR_hvS67s,4122
|
|
30
34
|
zrb/builtin/md5.py,sha256=690RV2LbW7wQeTFxY-lmmqTSVEEZv3XZbjEUW1Q3XpE,1480
|
|
31
35
|
zrb/builtin/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
36
|
zrb/builtin/project/add/fastapp/fastapp_input.py,sha256=MKlWR_LxWhM_DcULCtLfL_IjTxpDnDBkn9KIqNmajFs,310
|
|
@@ -378,7 +382,8 @@ zrb/task/llm/history_summarization.py,sha256=YqLm9Q5xzFVTL5XwnWu3XwaMgSxKskDFvoi
|
|
|
378
382
|
zrb/task/llm/print_node.py,sha256=d7OMOJ2kj6iXZS2cC5cyuDF1QDFsA9FxDGX-xfirvxs,8674
|
|
379
383
|
zrb/task/llm/prompt.py,sha256=jlFNWa1IMCYNQh9Ccq9DB1dq9964IOQJ5jNSC9rMpkw,11366
|
|
380
384
|
zrb/task/llm/subagent_conversation_history.py,sha256=y8UbKF2GpjB-r1bc2xCpzepriVEq0wfdyFuHOwfpPfk,1647
|
|
381
|
-
zrb/task/llm/
|
|
385
|
+
zrb/task/llm/tool_confirmation_completer.py,sha256=rOYZdq5No9DfLUG889cr99cUX6XmoO29Yh9FUcdMMio,1534
|
|
386
|
+
zrb/task/llm/tool_wrapper.py,sha256=FfW2JcZ8vUVie0pekpwpQABQ8DhIv8EG7_kPSYGwsVw,12440
|
|
382
387
|
zrb/task/llm/typing.py,sha256=c8VAuPBw_4A3DxfYdydkgedaP-LU61W9_wj3m3CAX1E,58
|
|
383
388
|
zrb/task/llm/workflow.py,sha256=eGpUXoIKgbPFPZJ7hHy_HkGYnGWWx9-B9FNjjNm3_8I,2968
|
|
384
389
|
zrb/task/llm_task.py,sha256=8tNtyufjsWUu5jJQEo9-dpl1N6fycA1ugllaKgLvOao,16571
|
|
@@ -432,7 +437,7 @@ zrb/util/truncate.py,sha256=eSzmjBpc1Qod3lM3M73snNbDOcARHukW_tq36dWdPvc,921
|
|
|
432
437
|
zrb/util/yaml.py,sha256=MXcqQTOYzx16onFxxg6HNdavxDK4RQjvKUi2NMKd19Y,6510
|
|
433
438
|
zrb/xcom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
434
439
|
zrb/xcom/xcom.py,sha256=W5BOF9w5fVX2ZBlbOTMsXHbY0yU8Advm-_oWJ3DJvDM,1824
|
|
435
|
-
zrb-1.21.
|
|
436
|
-
zrb-1.21.
|
|
437
|
-
zrb-1.21.
|
|
438
|
-
zrb-1.21.
|
|
440
|
+
zrb-1.21.31.dist-info/METADATA,sha256=LQK_9dOC8LRBm3Paivofzrd3MUWzaTIpXYpMk8p0suk,10622
|
|
441
|
+
zrb-1.21.31.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
442
|
+
zrb-1.21.31.dist-info/entry_points.txt,sha256=-Pg3ElWPfnaSM-XvXqCxEAa-wfVI6BEgcs386s8C8v8,46
|
|
443
|
+
zrb-1.21.31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|