wcgw 3.0.6__py3-none-any.whl → 4.0.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.
Potentially problematic release.
This version of wcgw might be problematic. Click here for more details.
- wcgw/client/bash_state/bash_state.py +182 -13
- wcgw/client/diff-instructions.txt +29 -15
- wcgw/client/file_ops/diff_edit.py +2 -1
- wcgw/client/file_ops/search_replace.py +37 -21
- wcgw/client/memory.py +5 -2
- wcgw/client/modes.py +7 -7
- wcgw/client/repo_ops/display_tree.py +3 -3
- wcgw/client/repo_ops/file_stats.py +152 -0
- wcgw/client/repo_ops/repo_context.py +122 -4
- wcgw/client/tool_prompts.py +13 -16
- wcgw/client/tools.py +500 -89
- wcgw/relay/serve.py +8 -52
- wcgw/types_.py +103 -16
- {wcgw-3.0.6.dist-info → wcgw-4.0.0.dist-info}/METADATA +59 -15
- {wcgw-3.0.6.dist-info → wcgw-4.0.0.dist-info}/RECORD +20 -19
- wcgw_cli/anthropic_client.py +1 -1
- wcgw_cli/openai_client.py +1 -1
- {wcgw-3.0.6.dist-info → wcgw-4.0.0.dist-info}/WHEEL +0 -0
- {wcgw-3.0.6.dist-info → wcgw-4.0.0.dist-info}/entry_points.txt +0 -0
- {wcgw-3.0.6.dist-info → wcgw-4.0.0.dist-info}/licenses/LICENSE +0 -0
wcgw/client/tools.py
CHANGED
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import subprocess
|
|
8
8
|
import traceback
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
+
from hashlib import sha256
|
|
10
11
|
from os.path import expanduser
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from tempfile import NamedTemporaryFile
|
|
@@ -28,6 +29,11 @@ from pydantic import BaseModel, TypeAdapter, ValidationError
|
|
|
28
29
|
from syntax_checker import check_syntax
|
|
29
30
|
|
|
30
31
|
from wcgw.client.bash_state.bash_state import get_status
|
|
32
|
+
from wcgw.client.repo_ops.file_stats import (
|
|
33
|
+
FileStats,
|
|
34
|
+
load_workspace_stats,
|
|
35
|
+
save_workspace_stats,
|
|
36
|
+
)
|
|
31
37
|
|
|
32
38
|
from ..types_ import (
|
|
33
39
|
BashCommand,
|
|
@@ -36,6 +42,7 @@ from ..types_ import (
|
|
|
36
42
|
Console,
|
|
37
43
|
ContextSave,
|
|
38
44
|
FileEdit,
|
|
45
|
+
FileWriteOrEdit,
|
|
39
46
|
Initialize,
|
|
40
47
|
Modes,
|
|
41
48
|
ModesConfig,
|
|
@@ -48,7 +55,12 @@ from .bash_state.bash_state import (
|
|
|
48
55
|
execute_bash,
|
|
49
56
|
)
|
|
50
57
|
from .encoder import EncoderDecoder, get_default_encoder
|
|
51
|
-
from .file_ops.search_replace import
|
|
58
|
+
from .file_ops.search_replace import (
|
|
59
|
+
DIVIDER_MARKER,
|
|
60
|
+
REPLACE_MARKER,
|
|
61
|
+
SEARCH_MARKER,
|
|
62
|
+
search_replace_edit,
|
|
63
|
+
)
|
|
52
64
|
from .memory import load_memory, save_memory
|
|
53
65
|
from .modes import (
|
|
54
66
|
ARCHITECT_PROMPT,
|
|
@@ -92,7 +104,7 @@ def initialize(
|
|
|
92
104
|
task_id_to_resume: str,
|
|
93
105
|
max_tokens: Optional[int],
|
|
94
106
|
mode: ModesConfig,
|
|
95
|
-
) -> tuple[str, Context]:
|
|
107
|
+
) -> tuple[str, Context, dict[str, list[tuple[int, int]]]]:
|
|
96
108
|
# Expand the workspace path
|
|
97
109
|
any_workspace_path = expand_user(any_workspace_path)
|
|
98
110
|
repo_context = ""
|
|
@@ -129,6 +141,7 @@ def initialize(
|
|
|
129
141
|
if not read_files_:
|
|
130
142
|
read_files_ = [any_workspace_path]
|
|
131
143
|
any_workspace_path = os.path.dirname(any_workspace_path)
|
|
144
|
+
# Let get_repo_context handle loading the workspace stats
|
|
132
145
|
repo_context, folder_to_start = get_repo_context(any_workspace_path, 50)
|
|
133
146
|
|
|
134
147
|
repo_context = f"---\n# Workspace structure\n{repo_context}\n---\n"
|
|
@@ -151,14 +164,18 @@ def initialize(
|
|
|
151
164
|
if loaded_state is not None:
|
|
152
165
|
try:
|
|
153
166
|
parsed_state = BashState.parse_state(loaded_state)
|
|
167
|
+
workspace_root = (
|
|
168
|
+
str(folder_to_start) if folder_to_start else parsed_state[5]
|
|
169
|
+
)
|
|
154
170
|
if mode == "wcgw":
|
|
155
171
|
context.bash_state.load_state(
|
|
156
172
|
parsed_state[0],
|
|
157
173
|
parsed_state[1],
|
|
158
174
|
parsed_state[2],
|
|
159
175
|
parsed_state[3],
|
|
160
|
-
parsed_state[4]
|
|
161
|
-
str(folder_to_start) if folder_to_start else
|
|
176
|
+
{**parsed_state[4], **context.bash_state.whitelist_for_overwrite},
|
|
177
|
+
str(folder_to_start) if folder_to_start else workspace_root,
|
|
178
|
+
workspace_root,
|
|
162
179
|
)
|
|
163
180
|
else:
|
|
164
181
|
state = modes_to_state(mode)
|
|
@@ -167,8 +184,9 @@ def initialize(
|
|
|
167
184
|
state[1],
|
|
168
185
|
state[2],
|
|
169
186
|
state[3],
|
|
170
|
-
parsed_state[4]
|
|
171
|
-
str(folder_to_start) if folder_to_start else
|
|
187
|
+
{**parsed_state[4], **context.bash_state.whitelist_for_overwrite},
|
|
188
|
+
str(folder_to_start) if folder_to_start else workspace_root,
|
|
189
|
+
workspace_root,
|
|
172
190
|
)
|
|
173
191
|
except ValueError:
|
|
174
192
|
context.console.print(traceback.format_exc())
|
|
@@ -178,12 +196,14 @@ def initialize(
|
|
|
178
196
|
else:
|
|
179
197
|
mode_changed = is_mode_change(mode, context.bash_state)
|
|
180
198
|
state = modes_to_state(mode)
|
|
199
|
+
# Use the provided workspace path as the workspace root
|
|
181
200
|
context.bash_state.load_state(
|
|
182
201
|
state[0],
|
|
183
202
|
state[1],
|
|
184
203
|
state[2],
|
|
185
204
|
state[3],
|
|
186
|
-
|
|
205
|
+
dict(context.bash_state.whitelist_for_overwrite),
|
|
206
|
+
str(folder_to_start) if folder_to_start else "",
|
|
187
207
|
str(folder_to_start) if folder_to_start else "",
|
|
188
208
|
)
|
|
189
209
|
if type == "first_call" or mode_changed:
|
|
@@ -194,13 +214,19 @@ def initialize(
|
|
|
194
214
|
del mode
|
|
195
215
|
|
|
196
216
|
initial_files_context = ""
|
|
217
|
+
initial_paths_with_ranges: dict[str, list[tuple[int, int]]] = {}
|
|
197
218
|
if read_files_:
|
|
198
219
|
if folder_to_start:
|
|
199
220
|
read_files_ = [
|
|
200
|
-
|
|
221
|
+
# Expand the path before checking if it's absolute
|
|
222
|
+
os.path.join(folder_to_start, f)
|
|
223
|
+
if not os.path.isabs(expand_user(f))
|
|
224
|
+
else expand_user(f)
|
|
201
225
|
for f in read_files_
|
|
202
226
|
]
|
|
203
|
-
initial_files = read_files(
|
|
227
|
+
initial_files, initial_paths_with_ranges, _ = read_files(
|
|
228
|
+
read_files_, max_tokens, context
|
|
229
|
+
)
|
|
204
230
|
initial_files_context = f"---\n# Requested files\n{initial_files}\n---\n"
|
|
205
231
|
|
|
206
232
|
uname_sysname = os.uname().sysname
|
|
@@ -225,7 +251,7 @@ Initialized in directory (also cwd): {context.bash_state.cwd}
|
|
|
225
251
|
|
|
226
252
|
global INITIALIZED
|
|
227
253
|
INITIALIZED = True
|
|
228
|
-
return output, context
|
|
254
|
+
return output, context, initial_paths_with_ranges
|
|
229
255
|
|
|
230
256
|
|
|
231
257
|
def is_mode_change(mode_config: ModesConfig, bash_state: BashState) -> bool:
|
|
@@ -264,7 +290,8 @@ def reset_wcgw(
|
|
|
264
290
|
file_edit_mode,
|
|
265
291
|
write_if_empty_mode,
|
|
266
292
|
mode,
|
|
267
|
-
|
|
293
|
+
dict(context.bash_state.whitelist_for_overwrite),
|
|
294
|
+
starting_directory,
|
|
268
295
|
starting_directory,
|
|
269
296
|
)
|
|
270
297
|
mode_prompt = get_mode_prompt(context)
|
|
@@ -288,7 +315,8 @@ def reset_wcgw(
|
|
|
288
315
|
file_edit_mode,
|
|
289
316
|
write_if_empty_mode,
|
|
290
317
|
mode,
|
|
291
|
-
|
|
318
|
+
dict(context.bash_state.whitelist_for_overwrite),
|
|
319
|
+
starting_directory,
|
|
292
320
|
starting_directory,
|
|
293
321
|
)
|
|
294
322
|
INITIALIZED = True
|
|
@@ -364,9 +392,10 @@ def truncate_if_over(content: str, max_tokens: Optional[int]) -> str:
|
|
|
364
392
|
|
|
365
393
|
|
|
366
394
|
def read_image_from_shell(file_path: str, context: Context) -> ImageData:
|
|
367
|
-
# Expand the path
|
|
395
|
+
# Expand the path before checking if it's absolute
|
|
368
396
|
file_path = expand_user(file_path)
|
|
369
397
|
|
|
398
|
+
# If not absolute after expansion, join with current working directory
|
|
370
399
|
if not os.path.isabs(file_path):
|
|
371
400
|
file_path = os.path.join(context.bash_state.cwd, file_path)
|
|
372
401
|
|
|
@@ -401,39 +430,138 @@ def write_file(
|
|
|
401
430
|
error_on_exist: bool,
|
|
402
431
|
max_tokens: Optional[int],
|
|
403
432
|
context: Context,
|
|
404
|
-
) ->
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
433
|
+
) -> tuple[
|
|
434
|
+
str, dict[str, list[tuple[int, int]]]
|
|
435
|
+
]: # Updated to return message and file paths with line ranges
|
|
436
|
+
# Expand the path before checking if it's absolute
|
|
437
|
+
path_ = expand_user(writefile.file_path)
|
|
438
|
+
|
|
439
|
+
workspace_path = context.bash_state.workspace_root
|
|
440
|
+
stats = load_workspace_stats(workspace_path)
|
|
441
|
+
|
|
442
|
+
if path_ not in stats.files:
|
|
443
|
+
stats.files[path_] = FileStats()
|
|
444
|
+
|
|
445
|
+
stats.files[path_].increment_write()
|
|
446
|
+
save_workspace_stats(workspace_path, stats)
|
|
447
|
+
|
|
448
|
+
if not os.path.isabs(path_):
|
|
449
|
+
return (
|
|
450
|
+
f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}",
|
|
451
|
+
{}, # Return empty dict instead of empty list for type consistency
|
|
452
|
+
)
|
|
409
453
|
|
|
410
454
|
error_on_exist_ = (
|
|
411
455
|
error_on_exist and path_ not in context.bash_state.whitelist_for_overwrite
|
|
412
456
|
)
|
|
413
457
|
|
|
458
|
+
if error_on_exist and path_ in context.bash_state.whitelist_for_overwrite:
|
|
459
|
+
# Ensure hash has not changed
|
|
460
|
+
if os.path.exists(path_):
|
|
461
|
+
with open(path_, "rb") as f:
|
|
462
|
+
file_content = f.read()
|
|
463
|
+
curr_hash = sha256(file_content).hexdigest()
|
|
464
|
+
|
|
465
|
+
whitelist_data = context.bash_state.whitelist_for_overwrite[path_]
|
|
466
|
+
|
|
467
|
+
# If we haven't fully read the file or hash has changed, require re-reading
|
|
468
|
+
if curr_hash != whitelist_data.file_hash:
|
|
469
|
+
error_on_exist_ = True
|
|
470
|
+
elif not whitelist_data.is_read_enough():
|
|
471
|
+
error_on_exist_ = True
|
|
472
|
+
|
|
414
473
|
# Validate using write_if_empty_mode after checking whitelist
|
|
415
474
|
allowed_globs = context.bash_state.write_if_empty_mode.allowed_globs
|
|
416
475
|
if allowed_globs != "all" and not any(
|
|
417
476
|
fnmatch.fnmatch(path_, pattern) for pattern in allowed_globs
|
|
418
477
|
):
|
|
419
|
-
return
|
|
478
|
+
return (
|
|
479
|
+
f"Error: updating file {path_} not allowed in current mode. Doesn't match allowed globs: {allowed_globs}",
|
|
480
|
+
{}, # Empty dict instead of empty list
|
|
481
|
+
)
|
|
420
482
|
|
|
421
|
-
add_overwrite_warning = ""
|
|
422
483
|
if (error_on_exist or error_on_exist_) and os.path.exists(path_):
|
|
423
484
|
content = Path(path_).read_text().strip()
|
|
424
485
|
if content:
|
|
425
|
-
content = truncate_if_over(content, max_tokens)
|
|
426
|
-
|
|
427
486
|
if error_on_exist_:
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
487
|
+
file_ranges = []
|
|
488
|
+
|
|
489
|
+
if path_ not in context.bash_state.whitelist_for_overwrite:
|
|
490
|
+
# File hasn't been read at all
|
|
491
|
+
msg = f"Error: you need to read existing file {path_} at least once before it can be overwritten.\n\n"
|
|
492
|
+
# Read the entire file
|
|
493
|
+
file_content_str, truncated, _, _, line_range = read_file(
|
|
494
|
+
path_, max_tokens, context, False
|
|
495
|
+
)
|
|
496
|
+
file_ranges = [line_range]
|
|
497
|
+
|
|
498
|
+
final_message = ""
|
|
499
|
+
if not truncated:
|
|
500
|
+
final_message = "You can now safely retry writing immediately considering the above information."
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
(
|
|
504
|
+
msg
|
|
505
|
+
+ f"Here's the existing file:\n```\n{file_content_str}\n{final_message}\n```"
|
|
506
|
+
),
|
|
507
|
+
{path_: file_ranges},
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
whitelist_data = context.bash_state.whitelist_for_overwrite[path_]
|
|
511
|
+
|
|
512
|
+
if curr_hash != whitelist_data.file_hash:
|
|
513
|
+
msg = "Error: the file has changed since last read.\n\n"
|
|
514
|
+
# Read the entire file again
|
|
515
|
+
file_content_str, truncated, _, _, line_range = read_file(
|
|
516
|
+
path_, max_tokens, context, False
|
|
517
|
+
)
|
|
518
|
+
file_ranges = [line_range]
|
|
519
|
+
|
|
520
|
+
final_message = ""
|
|
521
|
+
if not truncated:
|
|
522
|
+
final_message = "You can now safely retry writing immediately considering the above information."
|
|
523
|
+
|
|
524
|
+
return (
|
|
525
|
+
(
|
|
526
|
+
msg
|
|
527
|
+
+ f"Here's the existing file:\n```\n{file_content_str}\n```\n{final_message}"
|
|
528
|
+
),
|
|
529
|
+
{path_: file_ranges},
|
|
530
|
+
)
|
|
531
|
+
else:
|
|
532
|
+
# The file hasn't changed, but we haven't read enough of it
|
|
533
|
+
unread_ranges = whitelist_data.get_unread_ranges()
|
|
534
|
+
# Format the ranges as a string for display
|
|
535
|
+
ranges_str = ", ".join(
|
|
536
|
+
[f"{start}-{end}" for start, end in unread_ranges]
|
|
537
|
+
)
|
|
538
|
+
msg = f"Error: you need to read more of the file before it can be overwritten.\nUnread line ranges: {ranges_str}\n\n"
|
|
539
|
+
|
|
540
|
+
# Read just the unread ranges
|
|
541
|
+
paths_: list[str] = []
|
|
542
|
+
for start, end in unread_ranges:
|
|
543
|
+
paths_.append(path_ + ":" + f"{start}-{end}")
|
|
544
|
+
paths_readfiles = ReadFiles(
|
|
545
|
+
file_paths=paths_, show_line_numbers_reason=""
|
|
546
|
+
)
|
|
547
|
+
readfiles, file_ranges_dict, truncated = read_files(
|
|
548
|
+
paths_readfiles.file_paths,
|
|
549
|
+
max_tokens,
|
|
550
|
+
context,
|
|
551
|
+
show_line_numbers=False,
|
|
552
|
+
start_line_nums=paths_readfiles.start_line_nums,
|
|
553
|
+
end_line_nums=paths_readfiles.end_line_nums,
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
final_message = ""
|
|
557
|
+
if not truncated:
|
|
558
|
+
final_message = "Now that you have read the rest of the file, you can now safely immediately retry writing but consider the new information above."
|
|
559
|
+
|
|
560
|
+
return (
|
|
561
|
+
(msg + "\n" + readfiles + "\n" + final_message),
|
|
562
|
+
file_ranges_dict,
|
|
563
|
+
)
|
|
564
|
+
# No need to add to whitelist here - will be handled by get_tool_output
|
|
437
565
|
|
|
438
566
|
path = Path(path_)
|
|
439
567
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -442,7 +570,7 @@ def write_file(
|
|
|
442
570
|
with path.open("w") as f:
|
|
443
571
|
f.write(writefile.file_content)
|
|
444
572
|
except OSError as e:
|
|
445
|
-
return f"Error: {e}"
|
|
573
|
+
return f"Error: {e}", {}
|
|
446
574
|
|
|
447
575
|
extension = Path(path_).suffix.lstrip(".")
|
|
448
576
|
|
|
@@ -454,6 +582,9 @@ def write_file(
|
|
|
454
582
|
syntax_errors = check.description
|
|
455
583
|
|
|
456
584
|
if syntax_errors:
|
|
585
|
+
if extension in {"tsx", "ts"}:
|
|
586
|
+
syntax_errors += "\nNote: Ignore if 'tagged template literals' are used, they may raise false positive errors in tree-sitter."
|
|
587
|
+
|
|
457
588
|
context_for_errors = get_context_for_errors(
|
|
458
589
|
check.errors, writefile.file_content, max_tokens
|
|
459
590
|
)
|
|
@@ -471,19 +602,17 @@ Syntax errors:
|
|
|
471
602
|
except Exception:
|
|
472
603
|
pass
|
|
473
604
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
"\n---\nWarning: a file already existed and it's now overwritten. Was it a mistake? If yes please revert your action."
|
|
477
|
-
"\n---\n"
|
|
478
|
-
+ "Here's the previous content:\n```\n"
|
|
479
|
-
+ add_overwrite_warning
|
|
480
|
-
+ "\n```"
|
|
481
|
-
)
|
|
605
|
+
# Count the lines directly from the content we're writing
|
|
606
|
+
total_lines = writefile.file_content.count("\n") + 1
|
|
482
607
|
|
|
483
|
-
return "Success" + "".join(warnings)
|
|
608
|
+
return "Success" + "".join(warnings), {
|
|
609
|
+
path_: [(1, total_lines)]
|
|
610
|
+
} # Return the file path with line range along with success message
|
|
484
611
|
|
|
485
612
|
|
|
486
|
-
def do_diff_edit(
|
|
613
|
+
def do_diff_edit(
|
|
614
|
+
fedit: FileEdit, max_tokens: Optional[int], context: Context
|
|
615
|
+
) -> tuple[str, dict[str, list[tuple[int, int]]]]:
|
|
487
616
|
try:
|
|
488
617
|
return _do_diff_edit(fedit, max_tokens, context)
|
|
489
618
|
except Exception as e:
|
|
@@ -501,15 +630,27 @@ def do_diff_edit(fedit: FileEdit, max_tokens: Optional[int], context: Context) -
|
|
|
501
630
|
raise e
|
|
502
631
|
|
|
503
632
|
|
|
504
|
-
def _do_diff_edit(
|
|
633
|
+
def _do_diff_edit(
|
|
634
|
+
fedit: FileEdit, max_tokens: Optional[int], context: Context
|
|
635
|
+
) -> tuple[str, dict[str, list[tuple[int, int]]]]:
|
|
505
636
|
context.console.log(f"Editing file: {fedit.file_path}")
|
|
506
637
|
|
|
507
|
-
if
|
|
638
|
+
# Expand the path before checking if it's absolute
|
|
639
|
+
path_ = expand_user(fedit.file_path)
|
|
640
|
+
|
|
641
|
+
if not os.path.isabs(path_):
|
|
508
642
|
raise Exception(
|
|
509
643
|
f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}"
|
|
510
644
|
)
|
|
511
|
-
|
|
512
|
-
|
|
645
|
+
|
|
646
|
+
workspace_path = context.bash_state.workspace_root
|
|
647
|
+
stats = load_workspace_stats(workspace_path)
|
|
648
|
+
|
|
649
|
+
if path_ not in stats.files:
|
|
650
|
+
stats.files[path_] = FileStats()
|
|
651
|
+
|
|
652
|
+
stats.files[path_].increment_edit()
|
|
653
|
+
save_workspace_stats(workspace_path, stats)
|
|
513
654
|
|
|
514
655
|
# Validate using file_edit_mode
|
|
515
656
|
allowed_globs = context.bash_state.file_edit_mode.allowed_globs
|
|
@@ -520,8 +661,7 @@ def _do_diff_edit(fedit: FileEdit, max_tokens: Optional[int], context: Context)
|
|
|
520
661
|
f"Error: updating file {path_} not allowed in current mode. Doesn't match allowed globs: {allowed_globs}"
|
|
521
662
|
)
|
|
522
663
|
|
|
523
|
-
#
|
|
524
|
-
context.bash_state.add_to_whitelist_for_overwrite(path_)
|
|
664
|
+
# No need to add to whitelist here - will be handled by get_tool_output
|
|
525
665
|
|
|
526
666
|
if not os.path.exists(path_):
|
|
527
667
|
raise Exception(f"Error: file {path_} does not exist")
|
|
@@ -538,6 +678,9 @@ def _do_diff_edit(fedit: FileEdit, max_tokens: Optional[int], context: Context)
|
|
|
538
678
|
lines, apply_diff_to, context.console.log
|
|
539
679
|
)
|
|
540
680
|
|
|
681
|
+
# Count the lines just once - after the edit but before writing
|
|
682
|
+
total_lines = apply_diff_to.count("\n") + 1
|
|
683
|
+
|
|
541
684
|
with open(path_, "w") as f:
|
|
542
685
|
f.write(apply_diff_to)
|
|
543
686
|
|
|
@@ -550,31 +693,97 @@ def _do_diff_edit(fedit: FileEdit, max_tokens: Optional[int], context: Context)
|
|
|
550
693
|
context_for_errors = get_context_for_errors(
|
|
551
694
|
check.errors, apply_diff_to, max_tokens
|
|
552
695
|
)
|
|
696
|
+
if extension in {"tsx", "ts"}:
|
|
697
|
+
syntax_errors += "\nNote: Ignore if 'tagged template literals' are used, they may raise false positive errors in tree-sitter."
|
|
553
698
|
|
|
554
699
|
context.console.print(f"W: Syntax errors encountered: {syntax_errors}")
|
|
555
|
-
|
|
700
|
+
|
|
701
|
+
return (
|
|
702
|
+
f"""{comments}
|
|
556
703
|
---
|
|
557
|
-
|
|
704
|
+
Warning: tree-sitter reported syntax errors, please re-read the file and fix if there are any errors.
|
|
558
705
|
Syntax errors:
|
|
559
706
|
{syntax_errors}
|
|
560
707
|
|
|
561
708
|
{context_for_errors}
|
|
562
|
-
"""
|
|
709
|
+
""",
|
|
710
|
+
{path_: [(1, total_lines)]},
|
|
711
|
+
) # Return the file path with line range along with the warning message
|
|
563
712
|
except Exception:
|
|
564
713
|
pass
|
|
565
714
|
|
|
566
|
-
return comments
|
|
715
|
+
return comments, {
|
|
716
|
+
path_: [(1, total_lines)]
|
|
717
|
+
} # Return the file path with line range along with the edit comments
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _is_edit(content: str, percentage: int) -> bool:
|
|
721
|
+
lines = content.lstrip().split("\n")
|
|
722
|
+
if not lines:
|
|
723
|
+
return False
|
|
724
|
+
line = lines[0]
|
|
725
|
+
if SEARCH_MARKER.match(line):
|
|
726
|
+
return True
|
|
727
|
+
if percentage <= 50:
|
|
728
|
+
for line in lines:
|
|
729
|
+
if (
|
|
730
|
+
SEARCH_MARKER.match(line)
|
|
731
|
+
or DIVIDER_MARKER.match(line)
|
|
732
|
+
or REPLACE_MARKER.match(line)
|
|
733
|
+
):
|
|
734
|
+
return True
|
|
735
|
+
return False
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def file_writing(
|
|
739
|
+
file_writing_args: FileWriteOrEdit,
|
|
740
|
+
max_tokens: Optional[int],
|
|
741
|
+
context: Context,
|
|
742
|
+
) -> tuple[
|
|
743
|
+
str, dict[str, list[tuple[int, int]]]
|
|
744
|
+
]: # Updated to return message and file paths with line ranges
|
|
745
|
+
"""
|
|
746
|
+
Write or edit a file based on percentage of changes.
|
|
747
|
+
If percentage_changed > 50%, treat content as direct file content.
|
|
748
|
+
Otherwise, treat content as search/replace blocks.
|
|
749
|
+
"""
|
|
750
|
+
# Expand the path before checking if it's absolute
|
|
751
|
+
path_ = expand_user(file_writing_args.file_path)
|
|
752
|
+
if not os.path.isabs(path_):
|
|
753
|
+
return (
|
|
754
|
+
f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}",
|
|
755
|
+
{}, # Return empty dict instead of empty list for type consistency
|
|
756
|
+
)
|
|
567
757
|
|
|
758
|
+
# If file doesn't exist, always use direct file_content mode
|
|
759
|
+
content = file_writing_args.file_content_or_search_replace_blocks
|
|
760
|
+
|
|
761
|
+
if not _is_edit(content, file_writing_args.percentage_to_change):
|
|
762
|
+
# Use direct content mode (same as WriteIfEmpty)
|
|
763
|
+
result, paths = write_file(
|
|
764
|
+
WriteIfEmpty(
|
|
765
|
+
file_path=path_,
|
|
766
|
+
file_content=file_writing_args.file_content_or_search_replace_blocks,
|
|
767
|
+
),
|
|
768
|
+
True,
|
|
769
|
+
max_tokens,
|
|
770
|
+
context,
|
|
771
|
+
)
|
|
772
|
+
return result, paths
|
|
773
|
+
else:
|
|
774
|
+
# File exists and percentage <= 50, use search/replace mode
|
|
775
|
+
result, paths = do_diff_edit(
|
|
776
|
+
FileEdit(
|
|
777
|
+
file_path=path_,
|
|
778
|
+
file_edit_using_search_replace_blocks=file_writing_args.file_content_or_search_replace_blocks,
|
|
779
|
+
),
|
|
780
|
+
max_tokens,
|
|
781
|
+
context,
|
|
782
|
+
)
|
|
783
|
+
return result, paths
|
|
568
784
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
| WriteIfEmpty
|
|
572
|
-
| FileEdit
|
|
573
|
-
| ReadImage
|
|
574
|
-
| ReadFiles
|
|
575
|
-
| Initialize
|
|
576
|
-
| ContextSave
|
|
577
|
-
)
|
|
785
|
+
|
|
786
|
+
TOOLS = BashCommand | FileWriteOrEdit | ReadImage | ReadFiles | Initialize | ContextSave
|
|
578
787
|
|
|
579
788
|
|
|
580
789
|
def which_tool(args: str) -> TOOLS:
|
|
@@ -585,10 +794,8 @@ def which_tool(args: str) -> TOOLS:
|
|
|
585
794
|
def which_tool_name(name: str) -> Type[TOOLS]:
|
|
586
795
|
if name == "BashCommand":
|
|
587
796
|
return BashCommand
|
|
588
|
-
elif name == "
|
|
589
|
-
return
|
|
590
|
-
elif name == "FileEdit":
|
|
591
|
-
return FileEdit
|
|
797
|
+
elif name == "FileWriteOrEdit":
|
|
798
|
+
return FileWriteOrEdit
|
|
592
799
|
elif name == "ReadImage":
|
|
593
800
|
return ReadImage
|
|
594
801
|
elif name == "ReadFiles":
|
|
@@ -638,32 +845,80 @@ def get_tool_output(
|
|
|
638
845
|
output: tuple[str | ImageData, float]
|
|
639
846
|
TOOL_CALLS.append(arg)
|
|
640
847
|
|
|
848
|
+
# Initialize a dictionary to track file paths and line ranges
|
|
849
|
+
file_paths_with_ranges: dict[str, list[tuple[int, int]]] = {}
|
|
850
|
+
|
|
641
851
|
if isinstance(arg, BashCommand):
|
|
642
852
|
context.console.print("Calling execute bash tool")
|
|
643
853
|
if not INITIALIZED:
|
|
644
854
|
raise Exception("Initialize tool not called yet.")
|
|
645
855
|
|
|
646
|
-
|
|
856
|
+
output_str, cost = execute_bash(
|
|
647
857
|
context.bash_state, enc, arg, max_tokens, arg.wait_for_seconds
|
|
648
858
|
)
|
|
859
|
+
output = output_str, cost
|
|
649
860
|
elif isinstance(arg, WriteIfEmpty):
|
|
650
861
|
context.console.print("Calling write file tool")
|
|
651
862
|
if not INITIALIZED:
|
|
652
863
|
raise Exception("Initialize tool not called yet.")
|
|
653
864
|
|
|
654
|
-
|
|
865
|
+
result, write_paths = write_file(arg, True, max_tokens, context)
|
|
866
|
+
output = result, 0
|
|
867
|
+
# Add write paths with their ranges to our tracking dictionary
|
|
868
|
+
for path, ranges in write_paths.items():
|
|
869
|
+
if path in file_paths_with_ranges:
|
|
870
|
+
file_paths_with_ranges[path].extend(ranges)
|
|
871
|
+
else:
|
|
872
|
+
file_paths_with_ranges[path] = ranges.copy()
|
|
655
873
|
elif isinstance(arg, FileEdit):
|
|
656
874
|
context.console.print("Calling full file edit tool")
|
|
657
875
|
if not INITIALIZED:
|
|
658
876
|
raise Exception("Initialize tool not called yet.")
|
|
659
877
|
|
|
660
|
-
|
|
878
|
+
result, edit_paths = do_diff_edit(arg, max_tokens, context)
|
|
879
|
+
output = result, 0.0
|
|
880
|
+
# Add edit paths with their ranges to our tracking dictionary
|
|
881
|
+
for path, ranges in edit_paths.items():
|
|
882
|
+
if path in file_paths_with_ranges:
|
|
883
|
+
file_paths_with_ranges[path].extend(ranges)
|
|
884
|
+
else:
|
|
885
|
+
file_paths_with_ranges[path] = ranges.copy()
|
|
886
|
+
elif isinstance(arg, FileWriteOrEdit):
|
|
887
|
+
context.console.print("Calling file writing tool")
|
|
888
|
+
if not INITIALIZED:
|
|
889
|
+
raise Exception("Initialize tool not called yet.")
|
|
890
|
+
|
|
891
|
+
result, write_edit_paths = file_writing(arg, max_tokens, context)
|
|
892
|
+
output = result, 0.0
|
|
893
|
+
# Add write/edit paths with their ranges to our tracking dictionary
|
|
894
|
+
for path, ranges in write_edit_paths.items():
|
|
895
|
+
if path in file_paths_with_ranges:
|
|
896
|
+
file_paths_with_ranges[path].extend(ranges)
|
|
897
|
+
else:
|
|
898
|
+
file_paths_with_ranges[path] = ranges.copy()
|
|
661
899
|
elif isinstance(arg, ReadImage):
|
|
662
900
|
context.console.print("Calling read image tool")
|
|
663
|
-
|
|
901
|
+
image_data = read_image_from_shell(arg.file_path, context)
|
|
902
|
+
output = image_data, 0.0
|
|
664
903
|
elif isinstance(arg, ReadFiles):
|
|
665
904
|
context.console.print("Calling read file tool")
|
|
666
|
-
|
|
905
|
+
# Access line numbers through properties
|
|
906
|
+
result, file_ranges_dict, _ = read_files(
|
|
907
|
+
arg.file_paths,
|
|
908
|
+
max_tokens,
|
|
909
|
+
context,
|
|
910
|
+
bool(arg.show_line_numbers_reason),
|
|
911
|
+
arg.start_line_nums,
|
|
912
|
+
arg.end_line_nums,
|
|
913
|
+
)
|
|
914
|
+
output = result, 0.0
|
|
915
|
+
|
|
916
|
+
# Merge the new file ranges into our tracking dictionary
|
|
917
|
+
for path, ranges in file_ranges_dict.items():
|
|
918
|
+
if path in file_paths_with_ranges:
|
|
919
|
+
file_paths_with_ranges[path].extend(ranges)
|
|
920
|
+
else:
|
|
921
|
+
file_paths_with_ranges[path] = ranges
|
|
667
922
|
elif isinstance(arg, Initialize):
|
|
668
923
|
context.console.print("Calling initial info tool")
|
|
669
924
|
if arg.type == "user_asked_mode_change" or arg.type == "reset_shell":
|
|
@@ -685,7 +940,7 @@ def get_tool_output(
|
|
|
685
940
|
0.0,
|
|
686
941
|
)
|
|
687
942
|
else:
|
|
688
|
-
output_, context = initialize(
|
|
943
|
+
output_, context, init_paths = initialize(
|
|
689
944
|
arg.type,
|
|
690
945
|
context,
|
|
691
946
|
arg.any_workspace_path,
|
|
@@ -695,21 +950,31 @@ def get_tool_output(
|
|
|
695
950
|
arg.mode,
|
|
696
951
|
)
|
|
697
952
|
output = output_, 0.0
|
|
953
|
+
# Since init_paths is already a dictionary mapping file paths to line ranges,
|
|
954
|
+
# we just need to merge it with our tracking dictionary
|
|
955
|
+
for path, ranges in init_paths.items():
|
|
956
|
+
if path not in file_paths_with_ranges and os.path.exists(path):
|
|
957
|
+
file_paths_with_ranges[path] = ranges
|
|
958
|
+
elif path in file_paths_with_ranges:
|
|
959
|
+
file_paths_with_ranges[path].extend(ranges)
|
|
698
960
|
|
|
699
961
|
elif isinstance(arg, ContextSave):
|
|
700
962
|
context.console.print("Calling task memory tool")
|
|
701
963
|
relevant_files = []
|
|
702
964
|
warnings = ""
|
|
965
|
+
# Expand user in project root path
|
|
703
966
|
arg.project_root_path = os.path.expanduser(arg.project_root_path)
|
|
704
967
|
for fglob in arg.relevant_file_globs:
|
|
968
|
+
# Expand user in glob pattern before checking if it's absolute
|
|
705
969
|
fglob = expand_user(fglob)
|
|
970
|
+
# If not absolute after expansion, join with project root path
|
|
706
971
|
if not os.path.isabs(fglob) and arg.project_root_path:
|
|
707
972
|
fglob = os.path.join(arg.project_root_path, fglob)
|
|
708
973
|
globs = glob.glob(fglob, recursive=True)
|
|
709
974
|
relevant_files.extend(globs[:1000])
|
|
710
975
|
if not globs:
|
|
711
976
|
warnings += f"Warning: No files found for the glob: {fglob}\n"
|
|
712
|
-
relevant_files_data = read_files(relevant_files[:10_000], None, context)
|
|
977
|
+
relevant_files_data, _, _ = read_files(relevant_files[:10_000], None, context)
|
|
713
978
|
save_path = save_memory(
|
|
714
979
|
arg, relevant_files_data, context.bash_state.serialize()
|
|
715
980
|
)
|
|
@@ -724,6 +989,10 @@ def get_tool_output(
|
|
|
724
989
|
output = output_, 0.0
|
|
725
990
|
else:
|
|
726
991
|
raise ValueError(f"Unknown tool: {arg}")
|
|
992
|
+
|
|
993
|
+
if file_paths_with_ranges: # Only add to whitelist if we have paths
|
|
994
|
+
context.bash_state.add_to_whitelist_for_overwrite(file_paths_with_ranges)
|
|
995
|
+
|
|
727
996
|
if isinstance(output[0], str):
|
|
728
997
|
context.console.print(str(output[0]))
|
|
729
998
|
else:
|
|
@@ -737,13 +1006,64 @@ default_enc = get_default_encoder()
|
|
|
737
1006
|
curr_cost = 0.0
|
|
738
1007
|
|
|
739
1008
|
|
|
1009
|
+
def range_format(start_line_num: Optional[int], end_line_num: Optional[int]) -> str:
|
|
1010
|
+
st = "" if not start_line_num else str(start_line_num)
|
|
1011
|
+
end = "" if not end_line_num else str(end_line_num)
|
|
1012
|
+
if not st and not end:
|
|
1013
|
+
return ""
|
|
1014
|
+
return f":{st}-{end}"
|
|
1015
|
+
|
|
1016
|
+
|
|
740
1017
|
def read_files(
|
|
741
|
-
file_paths: list[str],
|
|
742
|
-
|
|
1018
|
+
file_paths: list[str],
|
|
1019
|
+
max_tokens: Optional[int],
|
|
1020
|
+
context: Context,
|
|
1021
|
+
show_line_numbers: bool = False,
|
|
1022
|
+
start_line_nums: Optional[list[Optional[int]]] = None,
|
|
1023
|
+
end_line_nums: Optional[list[Optional[int]]] = None,
|
|
1024
|
+
) -> tuple[
|
|
1025
|
+
str, dict[str, list[tuple[int, int]]], bool
|
|
1026
|
+
]: # Updated to return file paths with ranges
|
|
743
1027
|
message = ""
|
|
1028
|
+
file_ranges_dict: dict[
|
|
1029
|
+
str, list[tuple[int, int]]
|
|
1030
|
+
] = {} # Map file paths to line ranges
|
|
1031
|
+
|
|
1032
|
+
workspace_path = context.bash_state.workspace_root
|
|
1033
|
+
stats = load_workspace_stats(workspace_path)
|
|
1034
|
+
|
|
1035
|
+
for path_ in file_paths:
|
|
1036
|
+
path_ = expand_user(path_)
|
|
1037
|
+
if not os.path.isabs(path_):
|
|
1038
|
+
continue
|
|
1039
|
+
if path_ not in stats.files:
|
|
1040
|
+
stats.files[path_] = FileStats()
|
|
1041
|
+
|
|
1042
|
+
stats.files[path_].increment_read()
|
|
1043
|
+
save_workspace_stats(workspace_path, stats)
|
|
1044
|
+
truncated = False
|
|
744
1045
|
for i, file in enumerate(file_paths):
|
|
745
1046
|
try:
|
|
746
|
-
|
|
1047
|
+
# Use line numbers from parameters if provided
|
|
1048
|
+
start_line_num = None if start_line_nums is None else start_line_nums[i]
|
|
1049
|
+
end_line_num = None if end_line_nums is None else end_line_nums[i]
|
|
1050
|
+
|
|
1051
|
+
# For backward compatibility, we still need to extract line numbers from path
|
|
1052
|
+
# if they weren't provided as parameters
|
|
1053
|
+
content, truncated, tokens, path, line_range = read_file(
|
|
1054
|
+
file,
|
|
1055
|
+
max_tokens,
|
|
1056
|
+
context,
|
|
1057
|
+
show_line_numbers,
|
|
1058
|
+
start_line_num,
|
|
1059
|
+
end_line_num,
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
# Add file path with line range to dictionary
|
|
1063
|
+
if path in file_ranges_dict:
|
|
1064
|
+
file_ranges_dict[path].append(line_range)
|
|
1065
|
+
else:
|
|
1066
|
+
file_ranges_dict[path] = [line_range]
|
|
747
1067
|
except Exception as e:
|
|
748
1068
|
message += f"\n{file}: {str(e)}\n"
|
|
749
1069
|
continue
|
|
@@ -751,7 +1071,8 @@ def read_files(
|
|
|
751
1071
|
if max_tokens:
|
|
752
1072
|
max_tokens = max_tokens - tokens
|
|
753
1073
|
|
|
754
|
-
|
|
1074
|
+
range_formatted = range_format(start_line_num, end_line_num)
|
|
1075
|
+
message += f"\n{file}{range_formatted}\n```\n{content}\n"
|
|
755
1076
|
|
|
756
1077
|
if truncated or (max_tokens and max_tokens <= 0):
|
|
757
1078
|
not_reading = file_paths[i + 1 :]
|
|
@@ -760,15 +1081,21 @@ def read_files(
|
|
|
760
1081
|
break
|
|
761
1082
|
else:
|
|
762
1083
|
message += "```"
|
|
763
|
-
|
|
764
|
-
return message
|
|
1084
|
+
return message, file_ranges_dict, truncated
|
|
765
1085
|
|
|
766
1086
|
|
|
767
1087
|
def read_file(
|
|
768
|
-
file_path: str,
|
|
769
|
-
|
|
1088
|
+
file_path: str,
|
|
1089
|
+
max_tokens: Optional[int],
|
|
1090
|
+
context: Context,
|
|
1091
|
+
show_line_numbers: bool = False,
|
|
1092
|
+
start_line_num: Optional[int] = None,
|
|
1093
|
+
end_line_num: Optional[int] = None,
|
|
1094
|
+
) -> tuple[str, bool, int, str, tuple[int, int]]:
|
|
770
1095
|
context.console.print(f"Reading file: {file_path}")
|
|
771
1096
|
|
|
1097
|
+
# Line numbers are now passed as parameters, no need to parse from path
|
|
1098
|
+
|
|
772
1099
|
# Expand the path before checking if it's absolute
|
|
773
1100
|
file_path = expand_user(file_path)
|
|
774
1101
|
|
|
@@ -777,28 +1104,83 @@ def read_file(
|
|
|
777
1104
|
f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}"
|
|
778
1105
|
)
|
|
779
1106
|
|
|
780
|
-
context.bash_state.add_to_whitelist_for_overwrite(file_path)
|
|
781
|
-
|
|
782
1107
|
path = Path(file_path)
|
|
783
1108
|
if not path.exists():
|
|
784
1109
|
raise ValueError(f"Error: file {file_path} does not exist")
|
|
785
1110
|
|
|
1111
|
+
# Read all lines of the file
|
|
786
1112
|
with path.open("r") as f:
|
|
787
|
-
|
|
1113
|
+
all_lines = f.readlines(10_000_000)
|
|
1114
|
+
|
|
1115
|
+
if all_lines[-1].endswith("\n"):
|
|
1116
|
+
# Special handling of line counts because readlines doesn't consider last empty line as a separate line
|
|
1117
|
+
all_lines[-1] = all_lines[-1][:-1]
|
|
1118
|
+
all_lines.append("")
|
|
1119
|
+
|
|
1120
|
+
total_lines = len(all_lines)
|
|
1121
|
+
|
|
1122
|
+
# Apply line range filtering if specified
|
|
1123
|
+
start_idx = 0
|
|
1124
|
+
if start_line_num is not None:
|
|
1125
|
+
# Convert 1-indexed line number to 0-indexed
|
|
1126
|
+
start_idx = max(0, start_line_num - 1)
|
|
1127
|
+
|
|
1128
|
+
end_idx = len(all_lines)
|
|
1129
|
+
if end_line_num is not None:
|
|
1130
|
+
# end_line_num is inclusive, so we use min to ensure it's within bounds
|
|
1131
|
+
end_idx = min(len(all_lines), end_line_num)
|
|
1132
|
+
|
|
1133
|
+
# Convert back to 1-indexed line numbers for tracking
|
|
1134
|
+
effective_start = start_line_num if start_line_num is not None else 1
|
|
1135
|
+
effective_end = end_line_num if end_line_num is not None else total_lines
|
|
1136
|
+
|
|
1137
|
+
filtered_lines = all_lines[start_idx:end_idx]
|
|
1138
|
+
|
|
1139
|
+
# Create content with or without line numbers
|
|
1140
|
+
if show_line_numbers:
|
|
1141
|
+
content_lines = []
|
|
1142
|
+
for i, line in enumerate(filtered_lines, start=start_idx + 1):
|
|
1143
|
+
content_lines.append(f"{i} {line}")
|
|
1144
|
+
content = "".join(content_lines)
|
|
1145
|
+
else:
|
|
1146
|
+
content = "".join(filtered_lines)
|
|
788
1147
|
|
|
789
1148
|
truncated = False
|
|
790
1149
|
tokens_counts = 0
|
|
1150
|
+
|
|
1151
|
+
# Handle token limit if specified
|
|
791
1152
|
if max_tokens is not None:
|
|
792
1153
|
tokens = default_enc.encoder(content)
|
|
793
1154
|
tokens_counts = len(tokens)
|
|
1155
|
+
|
|
794
1156
|
if len(tokens) > max_tokens:
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1157
|
+
# Truncate at token boundary first
|
|
1158
|
+
truncated_tokens = tokens[:max_tokens]
|
|
1159
|
+
truncated_content = default_enc.decoder(truncated_tokens)
|
|
1160
|
+
|
|
1161
|
+
# Count how many lines we kept
|
|
1162
|
+
line_count = truncated_content.count("\n")
|
|
1163
|
+
|
|
1164
|
+
# Calculate the last line number shown (1-indexed)
|
|
1165
|
+
last_line_shown = start_idx + line_count
|
|
1166
|
+
|
|
1167
|
+
content = truncated_content
|
|
1168
|
+
# Add informative message about truncation with total line count
|
|
1169
|
+
total_lines = len(all_lines)
|
|
1170
|
+
content += f"\n(...truncated) Only showing till line number {last_line_shown} of {total_lines} total lines due to the token limit, please continue reading from {last_line_shown + 1} if required"
|
|
800
1171
|
truncated = True
|
|
801
|
-
|
|
1172
|
+
|
|
1173
|
+
# Update effective_end if truncated
|
|
1174
|
+
effective_end = last_line_shown
|
|
1175
|
+
|
|
1176
|
+
# Return the content along with the effective line range that was read
|
|
1177
|
+
return (
|
|
1178
|
+
content,
|
|
1179
|
+
truncated,
|
|
1180
|
+
tokens_counts,
|
|
1181
|
+
file_path,
|
|
1182
|
+
(effective_start, effective_end),
|
|
1183
|
+
)
|
|
802
1184
|
|
|
803
1185
|
|
|
804
1186
|
if __name__ == "__main__":
|
|
@@ -839,3 +1221,32 @@ if __name__ == "__main__":
|
|
|
839
1221
|
None,
|
|
840
1222
|
)
|
|
841
1223
|
)
|
|
1224
|
+
|
|
1225
|
+
print(
|
|
1226
|
+
get_tool_output(
|
|
1227
|
+
Context(BASH_STATE, BASH_STATE.console),
|
|
1228
|
+
ReadFiles(
|
|
1229
|
+
file_paths=["/Users/arusia/repos/wcgw/src/wcgw/client/tools.py"],
|
|
1230
|
+
show_line_numbers_reason="true",
|
|
1231
|
+
),
|
|
1232
|
+
default_enc,
|
|
1233
|
+
0,
|
|
1234
|
+
lambda x, y: ("", 0),
|
|
1235
|
+
15000,
|
|
1236
|
+
)[0][0]
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
print(
|
|
1240
|
+
get_tool_output(
|
|
1241
|
+
Context(BASH_STATE, BASH_STATE.console),
|
|
1242
|
+
FileWriteOrEdit(
|
|
1243
|
+
file_path="/Users/arusia/repos/wcgw/src/wcgw/client/tools.py",
|
|
1244
|
+
file_content_or_search_replace_blocks="""test""",
|
|
1245
|
+
percentage_to_change=100,
|
|
1246
|
+
),
|
|
1247
|
+
default_enc,
|
|
1248
|
+
0,
|
|
1249
|
+
lambda x, y: ("", 0),
|
|
1250
|
+
800,
|
|
1251
|
+
)[0][0]
|
|
1252
|
+
)
|