aider-ce 0.88.20__py3-none-any.whl → 0.88.38__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.
- aider/__init__.py +1 -1
- aider/_version.py +2 -2
- aider/args.py +63 -43
- aider/coders/agent_coder.py +331 -79
- aider/coders/agent_prompts.py +3 -15
- aider/coders/architect_coder.py +21 -5
- aider/coders/base_coder.py +661 -413
- aider/coders/base_prompts.py +6 -3
- aider/coders/chat_chunks.py +39 -17
- aider/commands.py +79 -15
- aider/diffs.py +10 -9
- aider/exceptions.py +1 -1
- aider/helpers/coroutines.py +8 -0
- aider/helpers/requests.py +45 -0
- aider/history.py +5 -0
- aider/io.py +179 -25
- aider/main.py +86 -35
- aider/models.py +16 -8
- aider/queries/tree-sitter-language-pack/c-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/clojure-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/cpp-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/csharp-tags.scm +6 -0
- aider/queries/tree-sitter-language-pack/dart-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elixir-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elm-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/go-tags.scm +7 -0
- aider/queries/tree-sitter-language-pack/java-tags.scm +6 -0
- aider/queries/tree-sitter-language-pack/javascript-tags.scm +8 -0
- aider/queries/tree-sitter-language-pack/lua-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/python-tags.scm +10 -0
- aider/queries/tree-sitter-language-pack/r-tags.scm +6 -0
- aider/queries/tree-sitter-language-pack/ruby-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/rust-tags.scm +3 -0
- aider/queries/tree-sitter-language-pack/solidity-tags.scm +1 -1
- aider/queries/tree-sitter-language-pack/swift-tags.scm +4 -1
- aider/queries/tree-sitter-languages/c-tags.scm +3 -0
- aider/queries/tree-sitter-languages/c_sharp-tags.scm +6 -0
- aider/queries/tree-sitter-languages/cpp-tags.scm +3 -0
- aider/queries/tree-sitter-languages/dart-tags.scm +2 -1
- aider/queries/tree-sitter-languages/elixir-tags.scm +5 -0
- aider/queries/tree-sitter-languages/elm-tags.scm +3 -0
- aider/queries/tree-sitter-languages/fortran-tags.scm +3 -0
- aider/queries/tree-sitter-languages/go-tags.scm +6 -0
- aider/queries/tree-sitter-languages/haskell-tags.scm +2 -0
- aider/queries/tree-sitter-languages/java-tags.scm +6 -0
- aider/queries/tree-sitter-languages/javascript-tags.scm +8 -0
- aider/queries/tree-sitter-languages/julia-tags.scm +2 -2
- aider/queries/tree-sitter-languages/kotlin-tags.scm +3 -0
- aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +6 -0
- aider/queries/tree-sitter-languages/php-tags.scm +6 -0
- aider/queries/tree-sitter-languages/python-tags.scm +10 -0
- aider/queries/tree-sitter-languages/ruby-tags.scm +5 -0
- aider/queries/tree-sitter-languages/rust-tags.scm +3 -0
- aider/queries/tree-sitter-languages/scala-tags.scm +2 -3
- aider/queries/tree-sitter-languages/typescript-tags.scm +3 -0
- aider/queries/tree-sitter-languages/zig-tags.scm +20 -3
- aider/repomap.py +71 -11
- aider/resources/model-metadata.json +27335 -635
- aider/resources/model-settings.yml +190 -0
- aider/scrape.py +2 -0
- aider/tools/__init__.py +2 -0
- aider/tools/command.py +84 -94
- aider/tools/command_interactive.py +95 -110
- aider/tools/delete_block.py +131 -159
- aider/tools/delete_line.py +97 -132
- aider/tools/delete_lines.py +120 -160
- aider/tools/extract_lines.py +288 -312
- aider/tools/finished.py +30 -43
- aider/tools/git_branch.py +107 -109
- aider/tools/git_diff.py +44 -56
- aider/tools/git_log.py +39 -53
- aider/tools/git_remote.py +37 -51
- aider/tools/git_show.py +33 -47
- aider/tools/git_status.py +30 -44
- aider/tools/grep.py +214 -242
- aider/tools/indent_lines.py +175 -201
- aider/tools/insert_block.py +220 -253
- aider/tools/list_changes.py +65 -80
- aider/tools/ls.py +64 -80
- aider/tools/make_editable.py +57 -73
- aider/tools/make_readonly.py +50 -66
- aider/tools/remove.py +64 -80
- aider/tools/replace_all.py +96 -109
- aider/tools/replace_line.py +118 -156
- aider/tools/replace_lines.py +160 -197
- aider/tools/replace_text.py +159 -160
- aider/tools/show_numbered_context.py +115 -141
- aider/tools/thinking.py +52 -0
- aider/tools/undo_change.py +78 -91
- aider/tools/update_todo_list.py +130 -138
- aider/tools/utils/base_tool.py +64 -0
- aider/tools/utils/output.py +118 -0
- aider/tools/view.py +38 -54
- aider/tools/view_files_matching.py +131 -134
- aider/tools/view_files_with_symbol.py +108 -120
- aider/urls.py +1 -1
- aider/versioncheck.py +4 -3
- aider/website/docs/config/adv-model-settings.md +237 -0
- aider/website/docs/config/agent-mode.md +36 -3
- aider/website/docs/config/model-aliases.md +2 -1
- aider/website/docs/faq.md +6 -11
- aider/website/docs/languages.md +2 -2
- aider/website/docs/more/infinite-output.md +27 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/METADATA +112 -70
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/RECORD +112 -107
- aider_ce-0.88.38.dist-info/entry_points.txt +6 -0
- aider_ce-0.88.20.dist-info/entry_points.txt +0 -2
- /aider/tools/{tool_utils.py → utils/helpers.py} +0 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/WHEEL +0 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/licenses/LICENSE.txt +0 -0
- {aider_ce-0.88.20.dist-info → aider_ce-0.88.38.dist-info}/top_level.txt +0 -0
aider/io.py
CHANGED
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import base64
|
|
3
3
|
import functools
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
import shutil
|
|
6
7
|
import signal
|
|
7
8
|
import subprocess
|
|
@@ -32,10 +33,13 @@ from rich.color import ColorParseError
|
|
|
32
33
|
from rich.columns import Columns
|
|
33
34
|
from rich.console import Console
|
|
34
35
|
from rich.markdown import Markdown
|
|
36
|
+
from rich.markup import escape
|
|
35
37
|
from rich.spinner import SPINNERS
|
|
36
38
|
from rich.style import Style as RichStyle
|
|
37
39
|
from rich.text import Text
|
|
38
40
|
|
|
41
|
+
from aider.helpers import coroutines
|
|
42
|
+
|
|
39
43
|
from .dump import dump # noqa: F401
|
|
40
44
|
from .editor import pipe_editor
|
|
41
45
|
from .utils import is_image_file, run_fzf
|
|
@@ -292,6 +296,13 @@ class InputOutput:
|
|
|
292
296
|
bell_on_next_input = False
|
|
293
297
|
notifications_command = None
|
|
294
298
|
encoding = "utf-8"
|
|
299
|
+
VALID_STYLES = {"bold", "red", "green", "blue", "orange"}
|
|
300
|
+
VALID_OPEN_TAG_PATTERN = re.compile(
|
|
301
|
+
r"\\\[(" + "|".join(re.escape(s) for s in VALID_STYLES) + r")\]"
|
|
302
|
+
)
|
|
303
|
+
VALID_CLOSE_TAG_PATTERN = re.compile(
|
|
304
|
+
r"\\\[/(?:" + "|".join(re.escape(s) for s in VALID_STYLES) + r"|)]"
|
|
305
|
+
)
|
|
295
306
|
|
|
296
307
|
def __init__(
|
|
297
308
|
self,
|
|
@@ -341,11 +352,14 @@ class InputOutput:
|
|
|
341
352
|
self.bell_on_next_input = False
|
|
342
353
|
self.notifications = notifications
|
|
343
354
|
self.verbose = verbose
|
|
355
|
+
self.profile_start_time = None
|
|
356
|
+
self.profile_last_time = None
|
|
344
357
|
|
|
345
358
|
# Variables used to interface with base_coder
|
|
346
359
|
self.coder = None
|
|
347
360
|
self.input_task = None
|
|
348
361
|
self.output_task = None
|
|
362
|
+
self.linear = False
|
|
349
363
|
|
|
350
364
|
# State tracking for confirmation input
|
|
351
365
|
self.confirmation_in_progress = False
|
|
@@ -414,14 +428,17 @@ class InputOutput:
|
|
|
414
428
|
self.chat_history_file = None
|
|
415
429
|
|
|
416
430
|
self.encoding = encoding
|
|
417
|
-
valid_line_endings = {"platform", "lf", "crlf"}
|
|
431
|
+
valid_line_endings = {"platform", "lf", "crlf", "preserve"}
|
|
418
432
|
if line_endings not in valid_line_endings:
|
|
419
433
|
raise ValueError(
|
|
420
434
|
f"Invalid line_endings value: {line_endings}. "
|
|
421
435
|
f"Must be one of: {', '.join(valid_line_endings)}"
|
|
422
436
|
)
|
|
437
|
+
self.line_endings = line_endings
|
|
423
438
|
self.newline = (
|
|
424
|
-
None
|
|
439
|
+
None
|
|
440
|
+
if line_endings in ("platform", "preserve")
|
|
441
|
+
else "\n" if line_endings == "lf" else "\r\n"
|
|
425
442
|
)
|
|
426
443
|
self.dry_run = dry_run
|
|
427
444
|
|
|
@@ -438,6 +455,7 @@ class InputOutput:
|
|
|
438
455
|
self.spinner_running = False
|
|
439
456
|
self.spinner_text = ""
|
|
440
457
|
self.last_spinner_text = ""
|
|
458
|
+
self.spinner_suffix = ""
|
|
441
459
|
self.spinner_frame_index = 0
|
|
442
460
|
self.spinner_last_frame_index = 0
|
|
443
461
|
self.unicode_palette = "░█"
|
|
@@ -505,6 +523,7 @@ class InputOutput:
|
|
|
505
523
|
self.spinner_running = True
|
|
506
524
|
self.spinner_text = text
|
|
507
525
|
self.spinner_frame_index = self.spinner_last_frame_index
|
|
526
|
+
self.spinner_suffix = ""
|
|
508
527
|
|
|
509
528
|
if update_last_text:
|
|
510
529
|
self.last_spinner_text = text
|
|
@@ -515,10 +534,18 @@ class InputOutput:
|
|
|
515
534
|
def update_spinner(self, text):
|
|
516
535
|
self.spinner_text = text
|
|
517
536
|
|
|
537
|
+
def update_spinner_suffix(self, text=None):
|
|
538
|
+
if text:
|
|
539
|
+
self.spinner_suffix = f" • {text[:16].strip()}"
|
|
540
|
+
else:
|
|
541
|
+
self.spinner_suffix = ""
|
|
542
|
+
|
|
518
543
|
def stop_spinner(self):
|
|
519
544
|
"""Stop the spinner."""
|
|
520
545
|
self.spinner_running = False
|
|
521
546
|
self.spinner_text = ""
|
|
547
|
+
self.spinner_suffix = ""
|
|
548
|
+
|
|
522
549
|
# Keep last frame index to avoid spinner "jumping" on restart
|
|
523
550
|
self.spinner_last_frame_index = self.spinner_frame_index
|
|
524
551
|
if self.fallback_spinner:
|
|
@@ -527,13 +554,13 @@ class InputOutput:
|
|
|
527
554
|
|
|
528
555
|
def get_bottom_toolbar(self):
|
|
529
556
|
"""Get the current spinner frame and text for the bottom toolbar."""
|
|
530
|
-
if not self.spinner_running or not self.spinner_frames:
|
|
557
|
+
if not self.spinner_running or not self.spinner_frames or self.linear:
|
|
531
558
|
return None
|
|
532
559
|
|
|
533
560
|
frame = self.spinner_frames[self.spinner_frame_index]
|
|
534
561
|
self.spinner_frame_index = (self.spinner_frame_index + 1) % len(self.spinner_frames)
|
|
535
562
|
|
|
536
|
-
return f"{frame} {self.spinner_text}"
|
|
563
|
+
return f"{frame} {self.spinner_text}{self.spinner_suffix}"
|
|
537
564
|
|
|
538
565
|
def _validate_color_settings(self):
|
|
539
566
|
"""Validate configured color strings and reset invalid ones."""
|
|
@@ -640,6 +667,18 @@ class InputOutput:
|
|
|
640
667
|
self.tool_error("Use --encoding to set the unicode encoding.")
|
|
641
668
|
return
|
|
642
669
|
|
|
670
|
+
def _detect_newline(self, filename):
|
|
671
|
+
try:
|
|
672
|
+
with open(filename, "rb") as f:
|
|
673
|
+
chunk = f.read(1024)
|
|
674
|
+
if b"\r\n" in chunk:
|
|
675
|
+
return "\r\n"
|
|
676
|
+
elif b"\n" in chunk:
|
|
677
|
+
return "\n"
|
|
678
|
+
except (FileNotFoundError, IsADirectoryError):
|
|
679
|
+
pass # File doesn't exist or is a directory, will use default
|
|
680
|
+
return None
|
|
681
|
+
|
|
643
682
|
def write_text(self, filename, content, max_retries=5, initial_delay=0.1):
|
|
644
683
|
"""
|
|
645
684
|
Writes content to a file, retrying with progressive backoff if the file is locked.
|
|
@@ -652,10 +691,14 @@ class InputOutput:
|
|
|
652
691
|
if self.dry_run:
|
|
653
692
|
return
|
|
654
693
|
|
|
694
|
+
newline = self.newline
|
|
695
|
+
if self.line_endings == "preserve":
|
|
696
|
+
newline = self._detect_newline(filename) or self.newline
|
|
697
|
+
|
|
655
698
|
delay = initial_delay
|
|
656
699
|
for attempt in range(max_retries):
|
|
657
700
|
try:
|
|
658
|
-
with open(str(filename), "w", encoding=self.encoding, newline=
|
|
701
|
+
with open(str(filename), "w", encoding=self.encoding, newline=newline) as f:
|
|
659
702
|
f.write(content)
|
|
660
703
|
return # Successfully wrote the file
|
|
661
704
|
except PermissionError as err:
|
|
@@ -694,9 +737,13 @@ class InputOutput:
|
|
|
694
737
|
# This method is now a no-op since we removed the confirmation_future logic
|
|
695
738
|
pass
|
|
696
739
|
|
|
740
|
+
def get_coder(self):
|
|
741
|
+
coder = self.coder() if self.coder else None
|
|
742
|
+
return coder
|
|
743
|
+
|
|
697
744
|
async def recreate_input(self, future=None):
|
|
698
|
-
if not
|
|
699
|
-
coder = self.
|
|
745
|
+
if not coroutines.is_active(self.input_task):
|
|
746
|
+
coder = self.get_coder()
|
|
700
747
|
|
|
701
748
|
if coder:
|
|
702
749
|
self.input_task = asyncio.create_task(coder.get_input())
|
|
@@ -720,10 +767,10 @@ class InputOutput:
|
|
|
720
767
|
show = ""
|
|
721
768
|
if rel_fnames:
|
|
722
769
|
rel_read_only_fnames = [
|
|
723
|
-
get_rel_fname(fname, root) for fname in
|
|
770
|
+
get_rel_fname(fname, root) for fname in abs_read_only_fnames or []
|
|
724
771
|
]
|
|
725
772
|
rel_read_only_stubs_fnames = [
|
|
726
|
-
get_rel_fname(fname, root) for fname in
|
|
773
|
+
get_rel_fname(fname, root) for fname in abs_read_only_stubs_fnames or []
|
|
727
774
|
]
|
|
728
775
|
show = self.format_files_for_input(
|
|
729
776
|
rel_fnames, rel_read_only_fnames, rel_read_only_stubs_fnames
|
|
@@ -886,9 +933,14 @@ class InputOutput:
|
|
|
886
933
|
return cmd
|
|
887
934
|
|
|
888
935
|
except EOFError:
|
|
889
|
-
|
|
936
|
+
coder = self.get_coder()
|
|
937
|
+
|
|
938
|
+
if coder:
|
|
939
|
+
await coder.commands.cmd_exit(None)
|
|
940
|
+
else:
|
|
941
|
+
raise SystemExit
|
|
942
|
+
|
|
890
943
|
except KeyboardInterrupt:
|
|
891
|
-
await self.cancel_output_task()
|
|
892
944
|
self.console.print()
|
|
893
945
|
return ""
|
|
894
946
|
except UnicodeEncodeError as err:
|
|
@@ -954,26 +1006,46 @@ class InputOutput:
|
|
|
954
1006
|
self.user_input(inp)
|
|
955
1007
|
return inp
|
|
956
1008
|
|
|
957
|
-
async def
|
|
1009
|
+
async def stop_input_task(self):
|
|
958
1010
|
if self.input_task:
|
|
959
1011
|
input_task = self.input_task
|
|
960
1012
|
self.input_task = None
|
|
961
1013
|
try:
|
|
962
1014
|
input_task.cancel()
|
|
963
1015
|
await input_task
|
|
964
|
-
except (
|
|
1016
|
+
except (
|
|
1017
|
+
asyncio.CancelledError,
|
|
1018
|
+
Exception,
|
|
1019
|
+
EOFError,
|
|
1020
|
+
IndexError,
|
|
1021
|
+
RuntimeError,
|
|
1022
|
+
SystemExit,
|
|
1023
|
+
):
|
|
965
1024
|
pass
|
|
966
1025
|
|
|
967
|
-
async def
|
|
1026
|
+
async def stop_output_task(self):
|
|
968
1027
|
if self.output_task:
|
|
969
1028
|
output_task = self.output_task
|
|
970
1029
|
self.output_task = None
|
|
971
1030
|
try:
|
|
972
1031
|
output_task.cancel()
|
|
973
1032
|
await output_task
|
|
974
|
-
except (
|
|
1033
|
+
except (
|
|
1034
|
+
asyncio.CancelledError,
|
|
1035
|
+
Exception,
|
|
1036
|
+
EOFError,
|
|
1037
|
+
IndexError,
|
|
1038
|
+
RuntimeError,
|
|
1039
|
+
SystemExit,
|
|
1040
|
+
):
|
|
975
1041
|
pass
|
|
976
1042
|
|
|
1043
|
+
async def stop_task_streams(self):
|
|
1044
|
+
input_task = asyncio.create_task(self.stop_input_task())
|
|
1045
|
+
output_task = asyncio.create_task(self.stop_output_task())
|
|
1046
|
+
|
|
1047
|
+
await asyncio.wait([input_task, output_task], return_when=asyncio.ALL_COMPLETED)
|
|
1048
|
+
|
|
977
1049
|
def add_to_input_history(self, inp):
|
|
978
1050
|
if not self.input_history_file:
|
|
979
1051
|
return
|
|
@@ -1042,6 +1114,24 @@ class InputOutput:
|
|
|
1042
1114
|
hist = "\n" + content.strip() + "\n\n"
|
|
1043
1115
|
self.append_chat_history(hist)
|
|
1044
1116
|
|
|
1117
|
+
def edit_in_editor(self, content):
|
|
1118
|
+
import subprocess
|
|
1119
|
+
import tempfile
|
|
1120
|
+
|
|
1121
|
+
with tempfile.NamedTemporaryFile(
|
|
1122
|
+
suffix=".md", mode="w", delete=False, encoding=self.encoding
|
|
1123
|
+
) as tmpfile:
|
|
1124
|
+
tmpfile.write(content)
|
|
1125
|
+
tmpfile.flush()
|
|
1126
|
+
editor = os.environ.get("EDITOR", "vi")
|
|
1127
|
+
subprocess.call([editor, tmpfile.name])
|
|
1128
|
+
|
|
1129
|
+
with open(tmpfile.name, "r", encoding=self.encoding) as f:
|
|
1130
|
+
edited = f.read()
|
|
1131
|
+
|
|
1132
|
+
os.unlink(tmpfile.name)
|
|
1133
|
+
return edited
|
|
1134
|
+
|
|
1045
1135
|
async def offer_url(
|
|
1046
1136
|
self, url, prompt="Open URL for more info?", allow_never=True, acknowledge=False
|
|
1047
1137
|
):
|
|
@@ -1092,6 +1182,7 @@ class InputOutput:
|
|
|
1092
1182
|
group=None,
|
|
1093
1183
|
group_response=None,
|
|
1094
1184
|
allow_never=False,
|
|
1185
|
+
allow_tweak=False,
|
|
1095
1186
|
acknowledge=False,
|
|
1096
1187
|
):
|
|
1097
1188
|
self.num_user_asks += 1
|
|
@@ -1110,6 +1201,9 @@ class InputOutput:
|
|
|
1110
1201
|
valid_responses = ["yes", "no", "skip", "all"]
|
|
1111
1202
|
options = " (Y)es/(N)o"
|
|
1112
1203
|
|
|
1204
|
+
if allow_tweak:
|
|
1205
|
+
valid_responses.append("tweak")
|
|
1206
|
+
options += "/(T)weak"
|
|
1113
1207
|
if group or group_response:
|
|
1114
1208
|
if not explicit_yes_required or group_response:
|
|
1115
1209
|
options += "/(A)ll"
|
|
@@ -1153,11 +1247,7 @@ class InputOutput:
|
|
|
1153
1247
|
if self.prompt_session:
|
|
1154
1248
|
await self.recreate_input()
|
|
1155
1249
|
|
|
1156
|
-
if (
|
|
1157
|
-
self.input_task
|
|
1158
|
-
and not self.input_task.done()
|
|
1159
|
-
and not self.input_task.cancelled()
|
|
1160
|
-
):
|
|
1250
|
+
if coroutines.is_active(self.input_task):
|
|
1161
1251
|
self.prompt_session.message = question
|
|
1162
1252
|
self.prompt_session.app.invalidate()
|
|
1163
1253
|
else:
|
|
@@ -1200,6 +1290,9 @@ class InputOutput:
|
|
|
1200
1290
|
self.append_chat_history(hist, linebreak=True, blockquote=True)
|
|
1201
1291
|
return False
|
|
1202
1292
|
|
|
1293
|
+
if res == "t":
|
|
1294
|
+
return "tweak"
|
|
1295
|
+
|
|
1203
1296
|
if explicit_yes_required and not group_response:
|
|
1204
1297
|
is_yes = res == "y"
|
|
1205
1298
|
else:
|
|
@@ -1314,7 +1407,7 @@ class InputOutput:
|
|
|
1314
1407
|
if log_only:
|
|
1315
1408
|
return
|
|
1316
1409
|
|
|
1317
|
-
messages = list(map(
|
|
1410
|
+
messages = list(map(lambda message: self.escape(message), messages))
|
|
1318
1411
|
style = dict()
|
|
1319
1412
|
if self.pretty:
|
|
1320
1413
|
if self.tool_output_color:
|
|
@@ -1326,6 +1419,49 @@ class InputOutput:
|
|
|
1326
1419
|
|
|
1327
1420
|
self.stream_print(*messages, style=style)
|
|
1328
1421
|
|
|
1422
|
+
def escape(self, text):
|
|
1423
|
+
"""Formats valid Rich tags and prints invalid ones as literal text using a single regex pass."""
|
|
1424
|
+
|
|
1425
|
+
# 1. Escape everything initially using Rich's built-in function
|
|
1426
|
+
escaped_text = escape(text)
|
|
1427
|
+
|
|
1428
|
+
if text == escaped_text:
|
|
1429
|
+
return Text(text)
|
|
1430
|
+
|
|
1431
|
+
# 2. Un-escape ONLY the valid opening tags
|
|
1432
|
+
# Replaces '\[style]' with '[style]'
|
|
1433
|
+
unescaped_text = self.__class__.VALID_OPEN_TAG_PATTERN.sub(
|
|
1434
|
+
lambda m: f"[{m.group(1)}]", escaped_text
|
|
1435
|
+
)
|
|
1436
|
+
|
|
1437
|
+
# 3. Un-escape ONLY the valid closing tags (handles both [/style] and [/] formats)
|
|
1438
|
+
# Replaces '\[/style]' or '\[/]' with '[/]'
|
|
1439
|
+
final_text = self.__class__.VALID_CLOSE_TAG_PATTERN.sub(r"[/]", unescaped_text)
|
|
1440
|
+
|
|
1441
|
+
# 4. Print the result
|
|
1442
|
+
return Text(final_text)
|
|
1443
|
+
|
|
1444
|
+
def profile(self, *messages, start=False):
|
|
1445
|
+
if not self.verbose:
|
|
1446
|
+
return
|
|
1447
|
+
|
|
1448
|
+
now = time.time()
|
|
1449
|
+
message_str = " ".join(map(str, messages))
|
|
1450
|
+
|
|
1451
|
+
# Treat uninitialized as an implicit start.
|
|
1452
|
+
if start or self.profile_start_time is None:
|
|
1453
|
+
self.profile_start_time = now
|
|
1454
|
+
self.stream_print(f"PROFILE: {message_str}")
|
|
1455
|
+
else:
|
|
1456
|
+
total_elapsed = now - self.profile_start_time
|
|
1457
|
+
last_elapsed = now - self.profile_last_time
|
|
1458
|
+
output_message = (
|
|
1459
|
+
f"PROFILE: [+{last_elapsed:6.2f}s] {message_str} (total {total_elapsed:.2f}s)"
|
|
1460
|
+
)
|
|
1461
|
+
self.stream_print(output_message)
|
|
1462
|
+
|
|
1463
|
+
self.profile_last_time = now
|
|
1464
|
+
|
|
1329
1465
|
def assistant_output(self, message, pretty=None):
|
|
1330
1466
|
if not message:
|
|
1331
1467
|
self.tool_warning("Empty response received from LLM. Check your provider account?")
|
|
@@ -1385,14 +1521,32 @@ class InputOutput:
|
|
|
1385
1521
|
|
|
1386
1522
|
self._stream_buffer = incomplete_line
|
|
1387
1523
|
|
|
1524
|
+
should_print = False
|
|
1525
|
+
should_reset = False
|
|
1526
|
+
|
|
1388
1527
|
if not final:
|
|
1389
1528
|
if len(lines) > 1:
|
|
1529
|
+
should_print = True
|
|
1530
|
+
else:
|
|
1531
|
+
# Ensure any remaining buffered content is printed using the full response
|
|
1532
|
+
should_print = True
|
|
1533
|
+
should_reset = True
|
|
1534
|
+
|
|
1535
|
+
if should_print:
|
|
1536
|
+
try:
|
|
1390
1537
|
self.console.print(
|
|
1391
1538
|
Text.from_ansi(output) if self.has_ansi_codes(output) else output
|
|
1392
1539
|
)
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1540
|
+
except Exception as e:
|
|
1541
|
+
if self.verbose:
|
|
1542
|
+
print(e)
|
|
1543
|
+
|
|
1544
|
+
self.console.print(
|
|
1545
|
+
(Text.from_ansi(output)) if self.has_ansi_codes(output) else output,
|
|
1546
|
+
markup=False,
|
|
1547
|
+
)
|
|
1548
|
+
|
|
1549
|
+
if should_reset:
|
|
1396
1550
|
self.reset_streaming_response()
|
|
1397
1551
|
|
|
1398
1552
|
def remove_consecutive_empty_strings(self, string_list):
|
|
@@ -1558,7 +1712,7 @@ class InputOutput:
|
|
|
1558
1712
|
return "\n".join(lines) + "\n"
|
|
1559
1713
|
|
|
1560
1714
|
output = StringIO()
|
|
1561
|
-
console = Console(file=output, force_terminal=False)
|
|
1715
|
+
console = Console(file=output, force_terminal=False, markup=False)
|
|
1562
1716
|
|
|
1563
1717
|
# Handle read-only files
|
|
1564
1718
|
if rel_read_only_fnames or rel_read_only_stubs_fnames:
|