wcgw 1.1.2__py3-none-any.whl → 1.2.1__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.

@@ -26,7 +26,8 @@ from ..types_ import (
26
26
  BashInteraction,
27
27
  CreateFileNew,
28
28
  FileEditFindReplace,
29
- FullFileEdit,
29
+ FileEdit,
30
+ ReadFile,
30
31
  ReadImage,
31
32
  Writefile,
32
33
  ResetShell,
@@ -175,6 +176,14 @@ def loop(
175
176
  - Send text input to the running program.
176
177
  - Send send_specials=["Enter"] to recheck status of a running program.
177
178
  - Only one of send_text, send_specials, send_ascii should be provided.
179
+ """,
180
+ ),
181
+ ToolParam(
182
+ input_schema=ReadFile.model_json_schema(),
183
+ name="ReadFile",
184
+ description="""
185
+ - Read full file content
186
+ - Provide absolute file path only
178
187
  """,
179
188
  ),
180
189
  ToolParam(
@@ -184,7 +193,7 @@ def loop(
184
193
  - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
185
194
  - This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
186
195
  - Provide absolute file path only.
187
- - For editing existing files, use FullFileEdit.
196
+ - For editing existing files, use FileEdit.
188
197
  """,
189
198
  ),
190
199
  ToolParam(
@@ -198,8 +207,8 @@ def loop(
198
207
  description="Resets the shell. Use only if all interrupts and prompt reset attempts have failed repeatedly.",
199
208
  ),
200
209
  ToolParam(
201
- input_schema=FullFileEdit.model_json_schema(),
202
- name="FullFileEdit",
210
+ input_schema=FileEdit.model_json_schema(),
211
+ name="FileEdit",
203
212
  description="""
204
213
  - Use absolute file path only.
205
214
  - Use SEARCH/REPLACE blocks to edit the file.
@@ -214,9 +223,9 @@ You're a cli assistant.
214
223
 
215
224
  Instructions:
216
225
 
217
- - You should use the provided bash execution tool to run script to complete objective.
218
- - Do not use sudo. Do not use interactive commands.
219
- - Ask user for confirmation before running anything major
226
+ - You should use the provided bash execution tool to run script to complete objective.
227
+ - First understand about the project by understanding the folder structure (ignoring node_modules or venv, etc.)
228
+ - Always read relevant files before making any changes.
220
229
 
221
230
  System information:
222
231
  - System: {uname_sysname}
@@ -2,14 +2,6 @@
2
2
  Instructions for
3
3
  Only edit the files using the following SEARCH/REPLACE blocks.
4
4
  ```
5
- <<<<<<< SEARCH
6
- =======
7
- def hello():
8
- "print a greeting"
9
-
10
- print("hello")
11
- >>>>>>> REPLACE
12
-
13
5
  <<<<<<< SEARCH
14
6
  def hello():
15
7
  "print a greeting"
@@ -23,16 +15,13 @@ from hello import hello
23
15
  # *SEARCH/REPLACE block* Rules:
24
16
 
25
17
  Every *SEARCH/REPLACE block* must use this format:
26
- 1. The start of search block: <<<<<<< SEARCH
27
- 2. A contiguous chunk of lines to search for in the existing source code
18
+ 1. The start of match block: <<<<<<< SEARCH
19
+ 2. A contiguous chunk of lines to do exact match for in the existing source code
28
20
  3. The dividing line: =======
29
21
  4. The lines to replace into the source code
30
22
  5. The end of the replace block: >>>>>>> REPLACE
31
23
 
32
- Use the *FULL* file path, as shown to you by the user.
33
-
34
- Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
35
- If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
24
+ Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
36
25
 
37
26
  *SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
38
27
  Including multiple unique *SEARCH/REPLACE* blocks if needed.
@@ -41,4 +30,6 @@ Include enough lines in each SEARCH section to uniquely match each set of lines
41
30
  Keep *SEARCH/REPLACE* blocks concise.
42
31
  Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
43
32
  Include just the changing lines, and a few surrounding lines if needed for uniqueness.
44
- Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
33
+ Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
34
+
35
+ Include context lines before and after the code to edit for better matching in "<<<<<<< SEARCH". Recommended to add 3 lines on each side.
@@ -24,8 +24,9 @@ from ..types_ import (
24
24
  BashCommand,
25
25
  BashInteraction,
26
26
  CreateFileNew,
27
- FullFileEdit,
27
+ FileEdit,
28
28
  ReadImage,
29
+ ReadFile,
29
30
  Writefile,
30
31
  ResetShell,
31
32
  )
@@ -58,7 +59,6 @@ from dotenv import load_dotenv
58
59
 
59
60
  class Config(BaseModel):
60
61
  model: Models
61
- secondary_model: Models
62
62
  cost_limit: float
63
63
  cost_file: dict[Models, CostData]
64
64
  cost_unit: str = "$"
@@ -146,10 +146,18 @@ def loop(
146
146
  waiting_for_assistant = history[-1]["role"] != "assistant"
147
147
 
148
148
  my_dir = os.path.dirname(__file__)
149
- config_file = os.path.join(my_dir, "..", "..", "..", "config.toml")
150
- with open(config_file) as f:
151
- config_json = toml.load(f)
152
- config = Config.model_validate(config_json)
149
+
150
+ config = Config(
151
+ model=cast(
152
+ Models, os.getenv("OPENAI_MODEL", "gpt-4o-2024-08-06").lower()),
153
+ cost_limit=0.1,
154
+ cost_unit="$",
155
+ cost_file={
156
+ "gpt-4o-2024-08-06": CostData(
157
+ cost_per_1m_input_tokens=5, cost_per_1m_output_tokens=15
158
+ ),
159
+ },
160
+ )
153
161
 
154
162
  if limit is not None:
155
163
  config.cost_limit = limit
@@ -177,7 +185,15 @@ def loop(
177
185
  - Interact with running program using this tool
178
186
  - Special keys like arrows, interrupts, enter, etc.
179
187
  - Send text input to the running program.
188
+ - Send send_specials=["Enter"] to recheck status of a running program.
180
189
  - Only one of send_text, send_specials, send_ascii should be provided.""",
190
+ ),
191
+ openai.pydantic_function_tool(
192
+ ReadFile,
193
+ description="""
194
+ - Read full file content
195
+ - Provide absolute file path only
196
+ """,
181
197
  ),
182
198
  openai.pydantic_function_tool(
183
199
  CreateFileNew,
@@ -185,14 +201,14 @@ def loop(
185
201
  - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
186
202
  - This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
187
203
  - Provide absolute file path only.
188
- - For editing existing files, use FullFileEdit.""",
204
+ - For editing existing files, use FileEdit.""",
189
205
  ),
190
206
  openai.pydantic_function_tool(
191
- FullFileEdit,
207
+ FileEdit,
192
208
  description="""
193
209
  - Use absolute file path only.
194
210
  - Use ONLY SEARCH/REPLACE blocks to edit the file.
195
- - file_edit_using_searh_replace_blocks should start with <<<<<<< SEARCH
211
+ - file_edit_using_search_replace_blocks should start with <<<<<<< SEARCH
196
212
  """,
197
213
  ),
198
214
  openai.pydantic_function_tool(
@@ -211,9 +227,9 @@ You're a cli assistant.
211
227
 
212
228
  Instructions:
213
229
 
214
- - You should use the provided bash execution tool to run script to complete objective.
215
- - Do not use sudo. Do not use interactive commands.
216
- - Ask user for confirmation before running anything major
230
+ - You should use the provided bash execution tool to run script to complete objective.
231
+ - First understand about the project by understanding the folder structure (ignoring node_modules or venv, etc.)
232
+ - Always read relevant files before making any changes.
217
233
 
218
234
  System information:
219
235
  - System: {uname_sysname}
wcgw/client/tools.py CHANGED
@@ -48,19 +48,17 @@ from openai.types.chat import (
48
48
  from nltk.metrics.distance import edit_distance
49
49
 
50
50
  from ..types_ import (
51
+ BashCommand,
52
+ BashInteraction,
51
53
  CreateFileNew,
52
54
  FileEditFindReplace,
53
- FullFileEdit,
55
+ FileEdit,
56
+ ReadFile,
57
+ ReadImage,
54
58
  ResetShell,
55
59
  Writefile,
56
60
  )
57
61
 
58
- from ..types_ import BashCommand
59
-
60
- from ..types_ import BashInteraction
61
-
62
- from ..types_ import ReadImage
63
-
64
62
  from .common import CostData, Models, discard_input
65
63
 
66
64
  from .openai_utils import get_input_cost, get_output_cost
@@ -320,7 +318,7 @@ def execute_bash(
320
318
  tokens = enc.encode(text)
321
319
 
322
320
  if max_tokens and len(tokens) >= max_tokens:
323
- text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
321
+ text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
324
322
 
325
323
  if is_interrupt:
326
324
  text = (
@@ -347,7 +345,7 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
347
345
 
348
346
  tokens = enc.encode(output)
349
347
  if max_tokens and len(tokens) >= max_tokens:
350
- output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
348
+ output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
351
349
 
352
350
  try:
353
351
  exit_status = get_status()
@@ -427,7 +425,7 @@ def read_image_from_shell(file_path: str) -> ImageData:
427
425
 
428
426
  def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
429
427
  if not os.path.isabs(writefile.file_path):
430
- return "Failure: file_path should be absolute path"
428
+ return f"Failure: file_path should be absolute path, current working directory is {CWD}"
431
429
  else:
432
430
  path_ = writefile.file_path
433
431
 
@@ -455,6 +453,14 @@ def find_least_edit_distance_substring(
455
453
  content_lines = [
456
454
  line.strip() for line in orig_content_lines
457
455
  ] # Remove trailing and leading space for calculating edit distance
456
+ new_to_original_indices = {}
457
+ new_content_lines = []
458
+ for i in range(len(content_lines)):
459
+ if not content_lines[i]:
460
+ continue
461
+ new_content_lines.append(content_lines[i])
462
+ new_to_original_indices[len(new_content_lines) - 1] = i
463
+ content_lines = new_content_lines
458
464
  find_lines = find_str.split("\n")
459
465
  find_lines = [
460
466
  line.strip() for line in find_lines
@@ -466,12 +472,15 @@ def find_least_edit_distance_substring(
466
472
  edit_distance_sum = 0
467
473
  for j in range(len(find_lines)):
468
474
  if (i + j) < len(content_lines):
469
- edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
475
+ edit_distance_sum += edit_distance(
476
+ content_lines[i + j], find_lines[j])
470
477
  else:
471
478
  edit_distance_sum += len(find_lines[j])
472
479
  if edit_distance_sum < min_edit_distance:
473
480
  min_edit_distance = edit_distance_sum
474
- min_edit_distance_lines = orig_content_lines[i : i + len(find_lines)]
481
+ orig_start_index = new_to_original_indices[i]
482
+ orig_end_index = new_to_original_indices.get(i + len(find_lines) - 1, len(orig_content_lines) - 1) + 1
483
+ min_edit_distance_lines = orig_content_lines[orig_start_index:orig_end_index]
475
484
  return "\n".join(min_edit_distance_lines), min_edit_distance
476
485
 
477
486
 
@@ -481,12 +490,12 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
481
490
  closest_match, min_edit_distance = find_least_edit_distance_substring(
482
491
  content, find_lines
483
492
  )
484
- print(
485
- f"Exact match not found, found with whitespace removed edit distance: {min_edit_distance}"
486
- )
487
- if min_edit_distance / len(find_lines) < 1 / 100:
488
- print("Editing file with closest match")
493
+ if min_edit_distance == 0:
489
494
  return edit_content(content, closest_match, replace_with_lines)
495
+ else:
496
+ print(
497
+ f"Exact match not found, found with whitespace removed edit distance: {min_edit_distance}"
498
+ )
490
499
  raise Exception(
491
500
  f"Error: no match found for the provided `find_lines` in the file. Closest match:\n---\n{closest_match}\n---\nFile not edited"
492
501
  )
@@ -495,11 +504,12 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
495
504
  return content
496
505
 
497
506
 
498
- def do_diff_edit(fedit: FullFileEdit) -> str:
507
+ def do_diff_edit(fedit: FileEdit) -> str:
499
508
  console.log(f"Editing file: {fedit.file_path}")
500
509
 
501
510
  if not os.path.isabs(fedit.file_path):
502
- raise Exception("Failure: file_path should be absolute path")
511
+ raise Exception(
512
+ f"Failure: file_path should be absolute path, current working directory is {CWD}")
503
513
  else:
504
514
  path_ = fedit.file_path
505
515
 
@@ -509,9 +519,10 @@ def do_diff_edit(fedit: FullFileEdit) -> str:
509
519
  with open(path_) as f:
510
520
  apply_diff_to = f.read()
511
521
 
512
- lines = fedit.file_edit_using_searh_replace_blocks.split("\n")
522
+ lines = fedit.file_edit_using_search_replace_blocks.split("\n")
513
523
  n_lines = len(lines)
514
524
  i = 0
525
+ replacement_count = 0
515
526
  while i < n_lines:
516
527
  if re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[i]):
517
528
  search_block = []
@@ -535,10 +546,17 @@ def do_diff_edit(fedit: FullFileEdit) -> str:
535
546
  search_block_ = "\n".join(search_block)
536
547
  replace_block_ = "\n".join(replace_block)
537
548
 
538
- apply_diff_to = edit_content(apply_diff_to, search_block_, replace_block_)
549
+ apply_diff_to = edit_content(
550
+ apply_diff_to, search_block_, replace_block_)
551
+ replacement_count += 1
539
552
  else:
540
553
  i += 1
541
554
 
555
+ if replacement_count == 0:
556
+ raise Exception(
557
+ "Error: no valid search-replace blocks found, please check your syntax for FileEdit"
558
+ )
559
+
542
560
  with open(path_, "w") as f:
543
561
  f.write(apply_diff_to)
544
562
 
@@ -547,7 +565,8 @@ def do_diff_edit(fedit: FullFileEdit) -> str:
547
565
 
548
566
  def file_edit(fedit: FileEditFindReplace) -> str:
549
567
  if not os.path.isabs(fedit.file_path):
550
- raise Exception("Failure: file_path should be absolute path")
568
+ raise Exception(
569
+ f"Failure: file_path should be absolute path, current working directory is {CWD}")
551
570
  else:
552
571
  path_ = fedit.file_path
553
572
 
@@ -557,14 +576,17 @@ def file_edit(fedit: FileEditFindReplace) -> str:
557
576
  if not fedit.find_lines:
558
577
  raise Exception("Error: `find_lines` cannot be empty")
559
578
 
560
- out_string = "\n".join("> " + line for line in fedit.find_lines.split("\n"))
561
- in_string = "\n".join("< " + line for line in fedit.replace_with_lines.split("\n"))
579
+ out_string = "\n".join(
580
+ "> " + line for line in fedit.find_lines.split("\n"))
581
+ in_string = "\n".join(
582
+ "< " + line for line in fedit.replace_with_lines.split("\n"))
562
583
  console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
563
584
  try:
564
585
  with open(path_) as f:
565
586
  content = f.read()
566
587
 
567
- content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
588
+ content = edit_content(content, fedit.find_lines,
589
+ fedit.replace_with_lines)
568
590
 
569
591
  with open(path_, "w") as f:
570
592
  f.write(content)
@@ -604,10 +626,11 @@ TOOLS = (
604
626
  | Writefile
605
627
  | CreateFileNew
606
628
  | FileEditFindReplace
607
- | FullFileEdit
629
+ | FileEdit
608
630
  | AIAssistant
609
631
  | DoneFlag
610
632
  | ReadImage
633
+ | ReadFile
611
634
  )
612
635
 
613
636
 
@@ -631,14 +654,16 @@ def which_tool_name(name: str) -> Type[TOOLS]:
631
654
  return CreateFileNew
632
655
  elif name == "FileEditFindReplace":
633
656
  return FileEditFindReplace
634
- elif name == "FullFileEdit":
635
- return FullFileEdit
657
+ elif name == "FileEdit":
658
+ return FileEdit
636
659
  elif name == "AIAssistant":
637
660
  return AIAssistant
638
661
  elif name == "DoneFlag":
639
662
  return DoneFlag
640
663
  elif name == "ReadImage":
641
664
  return ReadImage
665
+ elif name == "ReadFile":
666
+ return ReadFile
642
667
  else:
643
668
  raise ValueError(f"Unknown tool name: {name}")
644
669
 
@@ -652,10 +677,11 @@ def get_tool_output(
652
677
  | Writefile
653
678
  | CreateFileNew
654
679
  | FileEditFindReplace
655
- | FullFileEdit
680
+ | FileEdit
656
681
  | AIAssistant
657
682
  | DoneFlag
658
- | ReadImage,
683
+ | ReadImage
684
+ | ReadFile,
659
685
  enc: tiktoken.Encoding,
660
686
  limit: float,
661
687
  loop_call: Callable[[str, float], tuple[str, float]],
@@ -670,10 +696,11 @@ def get_tool_output(
670
696
  | Writefile
671
697
  | CreateFileNew
672
698
  | FileEditFindReplace
673
- | FullFileEdit
699
+ | FileEdit
674
700
  | AIAssistant
675
701
  | DoneFlag
676
702
  | ReadImage
703
+ | ReadFile
677
704
  ](
678
705
  Confirmation
679
706
  | BashCommand
@@ -682,10 +709,11 @@ def get_tool_output(
682
709
  | Writefile
683
710
  | CreateFileNew
684
711
  | FileEditFindReplace
685
- | FullFileEdit
712
+ | FileEdit
686
713
  | AIAssistant
687
714
  | DoneFlag
688
715
  | ReadImage
716
+ | ReadFile
689
717
  )
690
718
  arg = adapter.validate_python(args)
691
719
  else:
@@ -706,7 +734,7 @@ def get_tool_output(
706
734
  elif isinstance(arg, FileEditFindReplace):
707
735
  console.print("Calling file edit tool")
708
736
  output = file_edit(arg), 0.0
709
- elif isinstance(arg, FullFileEdit):
737
+ elif isinstance(arg, FileEdit):
710
738
  console.print("Calling full file edit tool")
711
739
  output = do_diff_edit(arg), 0.0
712
740
  elif isinstance(arg, DoneFlag):
@@ -718,6 +746,9 @@ def get_tool_output(
718
746
  elif isinstance(arg, ReadImage):
719
747
  console.print("Calling read image tool")
720
748
  output = read_image_from_shell(arg.file_path), 0.0
749
+ elif isinstance(arg, ReadFile):
750
+ console.print("Calling read file tool")
751
+ output = read_file(arg), 0.0
721
752
  elif isinstance(arg, ResetShell):
722
753
  console.print("Calling reset shell tool")
723
754
  output = reset_shell(), 0.0
@@ -732,7 +763,8 @@ History = list[ChatCompletionMessageParam]
732
763
 
733
764
  default_enc = tiktoken.encoding_for_model("gpt-4o")
734
765
  default_model: Models = "gpt-4o-2024-08-06"
735
- default_cost = CostData(cost_per_1m_input_tokens=0.15, cost_per_1m_output_tokens=0.6)
766
+ default_cost = CostData(cost_per_1m_input_tokens=0.15,
767
+ cost_per_1m_output_tokens=0.6)
736
768
  curr_cost = 0.0
737
769
 
738
770
 
@@ -744,8 +776,9 @@ class Mdata(BaseModel):
744
776
  | CreateFileNew
745
777
  | ResetShell
746
778
  | FileEditFindReplace
747
- | FullFileEdit
779
+ | FileEdit
748
780
  | str
781
+ | ReadFile
749
782
  )
750
783
 
751
784
 
@@ -774,7 +807,8 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
774
807
  raise Exception(mdata)
775
808
  try:
776
809
  output, cost = get_tool_output(
777
- mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
810
+ mdata.data, default_enc, 0.0, lambda x, y: (
811
+ "", 0), None
778
812
  )
779
813
  curr_cost += cost
780
814
  print(f"{curr_cost=}")
@@ -805,3 +839,19 @@ def app(
805
839
  exit()
806
840
 
807
841
  register_client(server_url, client_uuid or "")
842
+
843
+
844
+ def read_file(readfile: ReadFile) -> str:
845
+
846
+ console.print(f"Reading file: {readfile.file_path}")
847
+
848
+ if not os.path.isabs(readfile.file_path):
849
+ return f"Failure: file_path should be absolute path, current working directory is {CWD}"
850
+
851
+ path = Path(readfile.file_path)
852
+ if not path.exists():
853
+ return f"Error: file {readfile.file_path} does not exist"
854
+
855
+ with path.open("r") as f:
856
+ content = f.read()
857
+ return content
wcgw/relay/serve.py CHANGED
@@ -19,7 +19,8 @@ from ..types_ import (
19
19
  BashInteraction,
20
20
  CreateFileNew,
21
21
  FileEditFindReplace,
22
- FullFileEdit,
22
+ FileEdit,
23
+ ReadFile,
23
24
  ResetShell,
24
25
  Writefile,
25
26
  Specials,
@@ -34,7 +35,8 @@ class Mdata(BaseModel):
34
35
  | CreateFileNew
35
36
  | ResetShell
36
37
  | FileEditFindReplace
37
- | FullFileEdit
38
+ | FileEdit
39
+ | ReadFile
38
40
  | str
39
41
  )
40
42
  user_id: UUID
@@ -49,7 +51,7 @@ gpts: dict[UUID, Callable[[str], None]] = {}
49
51
  images: DefaultDict[UUID, dict[str, dict[str, Any]]] = DefaultDict(dict)
50
52
 
51
53
 
52
- CLIENT_VERSION_MINIMUM = "1.1.0"
54
+ CLIENT_VERSION_MINIMUM = "1.2.0"
53
55
 
54
56
 
55
57
  @app.websocket("/v1/register/{uuid}")
@@ -63,12 +65,14 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
63
65
  # receive client version
64
66
  client_version = await websocket.receive_text()
65
67
  sem_version_client = semantic_version.Version.coerce(client_version)
66
- sem_version_server = semantic_version.Version.coerce(CLIENT_VERSION_MINIMUM)
68
+ sem_version_server = semantic_version.Version.coerce(
69
+ CLIENT_VERSION_MINIMUM)
67
70
  if sem_version_client < sem_version_server:
68
71
  await websocket.send_text(
69
72
  Mdata(
70
73
  user_id=uuid,
71
- data=f"Client version {client_version} is outdated. Please upgrade to {CLIENT_VERSION_MINIMUM} or higher.",
74
+ data=f"Client version {client_version} is outdated. Please upgrade to {
75
+ CLIENT_VERSION_MINIMUM} or higher.",
72
76
  ).model_dump_json()
73
77
  )
74
78
  await websocket.close(
@@ -88,7 +92,8 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
88
92
  while True:
89
93
  received_data = await websocket.receive_text()
90
94
  if uuid not in gpts:
91
- raise fastapi.HTTPException(status_code=400, detail="No call made")
95
+ raise fastapi.HTTPException(
96
+ status_code=400, detail="No call made")
92
97
  gpts[uuid](received_data)
93
98
  except WebSocketDisconnect:
94
99
  # Remove the client if the WebSocket is disconnected
@@ -126,13 +131,13 @@ async def create_file(write_file_data: CreateFileNewWithUUID) -> str:
126
131
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
127
132
 
128
133
 
129
- class FullFileEditWithUUID(FullFileEdit):
134
+ class FileEditWithUUID(FileEdit):
130
135
  user_id: UUID
131
136
 
132
137
 
133
138
  @app.post("/v1/full_file_edit")
134
139
  async def file_edit_find_replace(
135
- file_edit_find_replace: FullFileEditWithUUID,
140
+ file_edit_find_replace: FileEditWithUUID,
136
141
  ) -> str:
137
142
  user_id = file_edit_find_replace.user_id
138
143
  if user_id not in clients:
@@ -257,6 +262,39 @@ async def bash_interaction(bash_interaction: BashInteractionWithUUID) -> str:
257
262
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
258
263
 
259
264
 
265
+ class ReadFileWithUUID(ReadFile):
266
+ user_id: UUID
267
+
268
+
269
+ @app.post("/v1/read_file")
270
+ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
271
+ user_id = read_file_data.user_id
272
+ if user_id not in clients:
273
+ return "Failure: id not found, ask the user to check it."
274
+
275
+ results: Optional[str] = None
276
+
277
+ def put_results(result: str) -> None:
278
+ nonlocal results
279
+ results = result
280
+
281
+ gpts[user_id] = put_results
282
+
283
+ await clients[user_id](
284
+ Mdata(data=ReadFile(file_path=read_file_data.file_path,
285
+ type=read_file_data.type
286
+ ), user_id=user_id)
287
+ )
288
+
289
+ start_time = time.time()
290
+ while time.time() - start_time < 30:
291
+ if results is not None:
292
+ return results
293
+ await asyncio.sleep(0.1)
294
+
295
+ raise fastapi.HTTPException(status_code=500, detail="Timeout error")
296
+
297
+
260
298
  app.mount("/static", StaticFiles(directory="static"), name="static")
261
299
 
262
300
 
wcgw/types_.py CHANGED
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from typing import Literal, Optional, Sequence
2
3
  from pydantic import BaseModel
3
4
 
@@ -35,7 +36,7 @@ class BashInteraction(BaseModel):
35
36
 
36
37
  class ReadImage(BaseModel):
37
38
  file_path: str
38
- type: Literal["ReadImage"] = "ReadImage"
39
+ type: Literal["ReadImage"]
39
40
 
40
41
 
41
42
  class Writefile(BaseModel):
@@ -48,6 +49,11 @@ class CreateFileNew(BaseModel):
48
49
  file_content: str
49
50
 
50
51
 
52
+ class ReadFile(BaseModel):
53
+ file_path: str # The path to the file to read
54
+ type: Literal["ReadFile"]
55
+
56
+
51
57
  class FileEditFindReplace(BaseModel):
52
58
  file_path: str
53
59
  find_lines: str
@@ -58,6 +64,13 @@ class ResetShell(BaseModel):
58
64
  should_reset: Literal[True] = True
59
65
 
60
66
 
61
- class FullFileEdit(BaseModel):
67
+ class FileEdit(BaseModel):
62
68
  file_path: str
63
- file_edit_using_searh_replace_blocks: str
69
+ file_edit_using_search_replace_blocks: str
70
+
71
+ def model_post_init(self, __context: object) -> None:
72
+ # Ensure first line is "<<<<<<< SEARCH"
73
+
74
+ if not re.match(r"^<<<<<<+\s*SEARCH\s*$", self.file_edit_using_search_replace_blocks.split("\n")[0]):
75
+
76
+ raise ValueError("First line of file_edit_using_search_replace_blocks must be '<<<<<<< SEARCH'")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 1.1.2
3
+ Version: 1.2.1
4
4
  Summary: What could go wrong giving full shell access to chatgpt?
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -0,0 +1,17 @@
1
+ wcgw/__init__.py,sha256=PNWvBvjUKA3aj4bHOtIqBKCAtOW88pr0hAXZ7RylVr8,68
2
+ wcgw/types_.py,sha256=a0sE4hPj4qylQFqMJwysLYy_FVYsueJTjq5fmHmNWGo,1855
3
+ wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ wcgw/client/__main__.py,sha256=ngI_vBcLAv7fJgmS4w4U7tuWtalGB8c7W5qebuT6Z6o,30
5
+ wcgw/client/anthropic_client.py,sha256=g5dj7tP_wkLMjQawil4PxhrEemlrIKYttWUXBI5SZ4Q,15260
6
+ wcgw/client/cli.py,sha256=Oja42CHkVO8puqOXflko9NeephYCMa85aBmQTEjBZtI,932
7
+ wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
8
+ wcgw/client/diff-instructions.txt,sha256=I_qroE6_aMpWtOeywPSDCTxmz-P6VPopdp1kR2r0D8w,1375
9
+ wcgw/client/openai_client.py,sha256=Pg72I4UfUQ-xrOkKlr4w1F1rc4KRvG0gePDO6E25-FM,17381
10
+ wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
11
+ wcgw/client/tools.py,sha256=zAWK0qAhVyVtWsW_v4fq2aU-l1I4c9i819VhFBAQ6-8,26416
12
+ wcgw/relay/serve.py,sha256=sMbERQTM1GhpnPWLzdtpcb23TIsgbP-ZuqVj-vjp5Rw,8186
13
+ wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
14
+ wcgw-1.2.1.dist-info/METADATA,sha256=CxTKyE6VJqJAlvnWY6RmxgrYmd__UVc3ciRfnd4-oSw,5255
15
+ wcgw-1.2.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
16
+ wcgw-1.2.1.dist-info/entry_points.txt,sha256=WlIB825-Vm9ZtNzgENQsbHj4DRMkbpVR7uSkQyBlaPA,93
17
+ wcgw-1.2.1.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- wcgw/__init__.py,sha256=PNWvBvjUKA3aj4bHOtIqBKCAtOW88pr0hAXZ7RylVr8,68
2
- wcgw/types_.py,sha256=-RbRO6aglz1tj014vvr6q8J8ly-tTacSu0hhask46qM,1424
3
- wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- wcgw/client/__main__.py,sha256=ngI_vBcLAv7fJgmS4w4U7tuWtalGB8c7W5qebuT6Z6o,30
5
- wcgw/client/anthropic_client.py,sha256=kswf7n1W7z1C8POWUnQLyESg7GtlgAoP-Yc4dCO86ts,14999
6
- wcgw/client/cli.py,sha256=Oja42CHkVO8puqOXflko9NeephYCMa85aBmQTEjBZtI,932
7
- wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
8
- wcgw/client/diff-instructions.txt,sha256=IcoPUZS5Y_fDlA5BuryIEGYykpnDtGUAP_oUNrZyC8U,1564
9
- wcgw/client/openai_client.py,sha256=Qu5xjfL3zP7YkdDwowtyq2kAGYY1-YvNOt8FwdScoU8,16972
10
- wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
11
- wcgw/client/tools.py,sha256=-Of_Kknaf27fi76rhDdMeDTuHjTw_AKSesPKCDDqAVM,24833
12
- wcgw/relay/serve.py,sha256=hUMNnf4KxF-clUYvky09bWQtuRvqJny71D93PppSpec,7236
13
- wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
14
- wcgw-1.1.2.dist-info/METADATA,sha256=f5t2PzR0Oz0BRTAMs2BwQC8Y5p7hXVUobut-PS1HIVQ,5255
15
- wcgw-1.1.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
16
- wcgw-1.1.2.dist-info/entry_points.txt,sha256=WlIB825-Vm9ZtNzgENQsbHj4DRMkbpVR7uSkQyBlaPA,93
17
- wcgw-1.1.2.dist-info/RECORD,,
File without changes