wcgw 1.0.1__py3-none-any.whl → 1.1.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.

wcgw/client/tools.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
2
  import base64
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from io import BytesIO
3
5
  import json
4
6
  import mimetypes
5
7
  from pathlib import Path
@@ -14,6 +16,7 @@ from typing import (
14
16
  NewType,
15
17
  Optional,
16
18
  ParamSpec,
19
+ Type,
17
20
  TypeVar,
18
21
  TypedDict,
19
22
  )
@@ -42,7 +45,14 @@ from openai.types.chat import (
42
45
  ParsedChatCompletionMessage,
43
46
  )
44
47
  from nltk.metrics.distance import edit_distance
45
- from ..types_ import FileEditFindReplace, ResetShell, Writefile
48
+
49
+ from ..types_ import (
50
+ CreateFileNew,
51
+ FileEditFindReplace,
52
+ FullFileEdit,
53
+ ResetShell,
54
+ Writefile,
55
+ )
46
56
 
47
57
  from ..types_ import BashCommand
48
58
 
@@ -71,7 +81,10 @@ def render_terminal_output(text: str) -> str:
71
81
  break
72
82
  else:
73
83
  i = len(dsp)
74
- return "\n".join(screen.display[: len(dsp) - i])
84
+ lines = screen.display[: len(dsp) - i]
85
+ # Strip trailing space
86
+ lines = [line.rstrip() for line in lines]
87
+ return "\n".join(lines)
75
88
 
76
89
 
77
90
  class Confirmation(BaseModel):
@@ -369,8 +382,16 @@ def serve_image_in_bg(file_path: str, client_uuid: str, name: str) -> None:
369
382
  serve_image_in_bg(file_path, client_uuid, name)
370
383
 
371
384
 
385
+ MEDIA_TYPES = Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
386
+
387
+
372
388
  class ImageData(BaseModel):
373
- dataurl: str
389
+ media_type: MEDIA_TYPES
390
+ data: str
391
+
392
+ @property
393
+ def dataurl(self) -> str:
394
+ return f"data:{self.media_type};base64," + self.data
374
395
 
375
396
 
376
397
  Param = ParamSpec("Param")
@@ -400,15 +421,20 @@ def read_image_from_shell(file_path: str) -> ImageData:
400
421
  image_bytes = image_file.read()
401
422
  image_b64 = base64.b64encode(image_bytes).decode("utf-8")
402
423
  image_type = mimetypes.guess_type(file_path)[0]
403
- return ImageData(dataurl=f"data:{image_type};base64,{image_b64}")
424
+ return ImageData(media_type=image_type, data=image_b64)
404
425
 
405
426
 
406
- def write_file(writefile: Writefile) -> str:
427
+ def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
407
428
  if not os.path.isabs(writefile.file_path):
408
429
  return "Failure: file_path should be absolute path"
409
430
  else:
410
431
  path_ = writefile.file_path
411
432
 
433
+ if error_on_exist and os.path.exists(path_):
434
+ file_data = Path(path_).read_text()
435
+ if file_data:
436
+ return f"Error: can't write to existing file {path_}, use other functions to edit the file"
437
+
412
438
  path = Path(path_)
413
439
  path.parent.mkdir(parents=True, exist_ok=True)
414
440
 
@@ -421,50 +447,128 @@ def write_file(writefile: Writefile) -> str:
421
447
  return "Success"
422
448
 
423
449
 
424
- def find_least_edit_distance_substring(content: str, find_str: str) -> str:
425
- content_lines = content.split("\n")
450
+ def find_least_edit_distance_substring(
451
+ content: str, find_str: str
452
+ ) -> tuple[str, float]:
453
+ orig_content_lines = content.split("\n")
454
+ content_lines = [
455
+ line.strip() for line in orig_content_lines
456
+ ] # Remove trailing and leading space for calculating edit distance
426
457
  find_lines = find_str.split("\n")
458
+ find_lines = [
459
+ line.strip() for line in find_lines
460
+ ] # Remove trailing and leading space for calculating edit distance
427
461
  # Slide window and find one with sum of edit distance least
428
462
  min_edit_distance = float("inf")
429
463
  min_edit_distance_lines = []
430
- for i in range(len(content_lines) - len(find_lines) + 1):
464
+ for i in range(max(1, len(content_lines) - len(find_lines) + 1)):
431
465
  edit_distance_sum = 0
432
466
  for j in range(len(find_lines)):
433
- edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
467
+ if (i + j) < len(content_lines):
468
+ edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
469
+ else:
470
+ edit_distance_sum += len(find_lines[j])
434
471
  if edit_distance_sum < min_edit_distance:
435
472
  min_edit_distance = edit_distance_sum
436
- min_edit_distance_lines = content_lines[i : i + len(find_lines)]
437
- return "\n".join(min_edit_distance_lines)
473
+ min_edit_distance_lines = orig_content_lines[i : i + len(find_lines)]
474
+ return "\n".join(min_edit_distance_lines), min_edit_distance
475
+
476
+
477
+ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
478
+ count = content.count(find_lines)
479
+ if count == 0:
480
+ closest_match, min_edit_distance = find_least_edit_distance_substring(
481
+ content, find_lines
482
+ )
483
+ print(
484
+ f"Exact match not found, found with whitespace removed edit distance: {min_edit_distance}"
485
+ )
486
+ if min_edit_distance / len(find_lines) < 1 / 100:
487
+ print("Editing file with closest match")
488
+ return edit_content(content, closest_match, replace_with_lines)
489
+ raise Exception(
490
+ f"Error: no match found for the provided `find_lines` in the file. Closest match:\n---\n{closest_match}\n---\nFile not edited"
491
+ )
492
+
493
+ content = content.replace(find_lines, replace_with_lines, 1)
494
+ return content
495
+
438
496
 
497
+ def do_diff_edit(fedit: FullFileEdit) -> str:
498
+ console.log(f"Editing file: {fedit.file_path}")
439
499
 
440
- def file_edit(file_edit: FileEditFindReplace) -> str:
441
- if not os.path.isabs(file_edit.file_path):
442
- path_ = os.path.join(CWD, file_edit.file_path)
500
+ if not os.path.isabs(fedit.file_path):
501
+ raise Exception("Failure: file_path should be absolute path")
443
502
  else:
444
- path_ = file_edit.file_path
503
+ path_ = fedit.file_path
504
+
505
+ if not os.path.exists(path_):
506
+ raise Exception(f"Error: file {path_} does not exist")
507
+
508
+ with open(path_) as f:
509
+ apply_diff_to = f.read()
510
+
511
+ lines = fedit.file_edit_using_searh_replace_blocks.split("\n")
512
+ n_lines = len(lines)
513
+ i = 0
514
+ while i < n_lines:
515
+ if re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[i]):
516
+ search_block = []
517
+ i += 1
518
+ while i < n_lines and not re.match(r"^======*\s*$", lines[i]):
519
+ search_block.append(lines[i])
520
+ i += 1
521
+ i += 1
522
+ replace_block = []
523
+ while i < n_lines and not re.match(r"^>>>>>>+\s*REPLACE\s*$", lines[i]):
524
+ replace_block.append(lines[i])
525
+ i += 1
526
+ i += 1
527
+
528
+ for line in search_block:
529
+ console.log("> " + line)
530
+ console.log("---")
531
+ for line in replace_block:
532
+ console.log("< " + line)
533
+
534
+ search_block_ = "\n".join(search_block)
535
+ replace_block_ = "\n".join(replace_block)
536
+
537
+ apply_diff_to = edit_content(apply_diff_to, search_block_, replace_block_)
538
+ else:
539
+ i += 1
445
540
 
446
- out_string = "\n".join("> " + line for line in file_edit.find_lines.split("\n"))
447
- in_string = "\n".join(
448
- "< " + line for line in file_edit.replace_with_lines.split("\n")
449
- )
541
+ with open(path_, "w") as f:
542
+ f.write(apply_diff_to)
543
+
544
+ return "Success"
545
+
546
+
547
+ def file_edit(fedit: FileEditFindReplace) -> str:
548
+ if not os.path.isabs(fedit.file_path):
549
+ raise Exception("Failure: file_path should be absolute path")
550
+ else:
551
+ path_ = fedit.file_path
552
+
553
+ if not os.path.exists(path_):
554
+ raise Exception(f"Error: file {path_} does not exist")
555
+
556
+ if not fedit.find_lines:
557
+ raise Exception("Error: `find_lines` cannot be empty")
558
+
559
+ out_string = "\n".join("> " + line for line in fedit.find_lines.split("\n"))
560
+ in_string = "\n".join("< " + line for line in fedit.replace_with_lines.split("\n"))
450
561
  console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
451
562
  try:
452
563
  with open(path_) as f:
453
564
  content = f.read()
454
- # First find counts
455
- count = content.count(file_edit.find_lines)
456
565
 
457
- if count == 0:
458
- closest_match = find_least_edit_distance_substring(
459
- content, file_edit.find_lines
460
- )
461
- return f"Error: no match found for the provided `find_lines` in the file. Closest match:\n---\n{closest_match}\n---\nFile not edited"
566
+ content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
462
567
 
463
- content = content.replace(file_edit.find_lines, file_edit.replace_with_lines)
464
568
  with open(path_, "w") as f:
465
569
  f.write(content)
466
570
  except OSError as e:
467
- return f"Error: {e}"
571
+ raise Exception(f"Error: {e}")
468
572
  console.print(f"File written to {path_}")
469
573
  return "Success"
470
574
 
@@ -491,31 +595,53 @@ def take_help_of_ai_assistant(
491
595
  return output, cost
492
596
 
493
597
 
494
- def which_tool(args: str) -> BaseModel:
495
- adapter = TypeAdapter[
496
- Confirmation
497
- | BashCommand
498
- | BashInteraction
499
- | ResetShell
500
- | Writefile
501
- | FileEditFindReplace
502
- | AIAssistant
503
- | DoneFlag
504
- | ReadImage
505
- ](
506
- Confirmation
507
- | BashCommand
508
- | BashInteraction
509
- | ResetShell
510
- | Writefile
511
- | FileEditFindReplace
512
- | AIAssistant
513
- | DoneFlag
514
- | ReadImage
515
- )
598
+ TOOLS = (
599
+ Confirmation
600
+ | BashCommand
601
+ | BashInteraction
602
+ | ResetShell
603
+ | Writefile
604
+ | CreateFileNew
605
+ | FileEditFindReplace
606
+ | FullFileEdit
607
+ | AIAssistant
608
+ | DoneFlag
609
+ | ReadImage
610
+ )
611
+
612
+
613
+ def which_tool(args: str) -> TOOLS:
614
+ adapter = TypeAdapter[TOOLS](TOOLS)
516
615
  return adapter.validate_python(json.loads(args))
517
616
 
518
617
 
618
+ def which_tool_name(name: str) -> Type[TOOLS]:
619
+ if name == "Confirmation":
620
+ return Confirmation
621
+ elif name == "BashCommand":
622
+ return BashCommand
623
+ elif name == "BashInteraction":
624
+ return BashInteraction
625
+ elif name == "ResetShell":
626
+ return ResetShell
627
+ elif name == "Writefile":
628
+ return Writefile
629
+ elif name == "CreateFileNew":
630
+ return CreateFileNew
631
+ elif name == "FileEditFindReplace":
632
+ return FileEditFindReplace
633
+ elif name == "FullFileEdit":
634
+ return FullFileEdit
635
+ elif name == "AIAssistant":
636
+ return AIAssistant
637
+ elif name == "DoneFlag":
638
+ return DoneFlag
639
+ elif name == "ReadImage":
640
+ return ReadImage
641
+ else:
642
+ raise ValueError(f"Unknown tool name: {name}")
643
+
644
+
519
645
  def get_tool_output(
520
646
  args: dict[object, object]
521
647
  | Confirmation
@@ -523,7 +649,9 @@ def get_tool_output(
523
649
  | BashInteraction
524
650
  | ResetShell
525
651
  | Writefile
652
+ | CreateFileNew
526
653
  | FileEditFindReplace
654
+ | FullFileEdit
527
655
  | AIAssistant
528
656
  | DoneFlag
529
657
  | ReadImage,
@@ -539,7 +667,9 @@ def get_tool_output(
539
667
  | BashInteraction
540
668
  | ResetShell
541
669
  | Writefile
670
+ | CreateFileNew
542
671
  | FileEditFindReplace
672
+ | FullFileEdit
543
673
  | AIAssistant
544
674
  | DoneFlag
545
675
  | ReadImage
@@ -549,7 +679,9 @@ def get_tool_output(
549
679
  | BashInteraction
550
680
  | ResetShell
551
681
  | Writefile
682
+ | CreateFileNew
552
683
  | FileEditFindReplace
684
+ | FullFileEdit
553
685
  | AIAssistant
554
686
  | DoneFlag
555
687
  | ReadImage
@@ -566,10 +698,16 @@ def get_tool_output(
566
698
  output = execute_bash(enc, arg, max_tokens)
567
699
  elif isinstance(arg, Writefile):
568
700
  console.print("Calling write file tool")
569
- output = write_file(arg), 0
701
+ output = write_file(arg, False), 0
702
+ elif isinstance(arg, CreateFileNew):
703
+ console.print("Calling write file tool")
704
+ output = write_file(arg, True), 0
570
705
  elif isinstance(arg, FileEditFindReplace):
571
706
  console.print("Calling file edit tool")
572
707
  output = file_edit(arg), 0.0
708
+ elif isinstance(arg, FullFileEdit):
709
+ console.print("Calling full file edit tool")
710
+ output = do_diff_edit(arg), 0.0
573
711
  elif isinstance(arg, DoneFlag):
574
712
  console.print("Calling mark finish tool")
575
713
  output = mark_finish(arg), 0.0
@@ -598,44 +736,30 @@ curr_cost = 0.0
598
736
 
599
737
 
600
738
  class Mdata(BaseModel):
601
- data: BashCommand | BashInteraction | Writefile | ResetShell | FileEditFindReplace
602
-
603
-
604
- execution_lock = threading.Lock()
605
-
606
-
607
- def execute_user_input() -> None:
608
- while True:
609
- discard_input()
610
- user_input = input()
611
- with execution_lock:
612
- try:
613
- console.log(
614
- execute_bash(
615
- default_enc,
616
- BashInteraction(
617
- send_ascii=[ord(x) for x in user_input] + [ord("\n")]
618
- ),
619
- max_tokens=None,
620
- )[0]
621
- )
622
- except Exception as e:
623
- traceback.print_exc()
624
- console.log(f"Error: {e}")
739
+ data: (
740
+ BashCommand
741
+ | BashInteraction
742
+ | Writefile
743
+ | CreateFileNew
744
+ | ResetShell
745
+ | FileEditFindReplace
746
+ | FullFileEdit
747
+ | str
748
+ )
625
749
 
626
750
 
627
- async def register_client(server_url: str, client_uuid: str = "") -> None:
751
+ def register_client(server_url: str, client_uuid: str = "") -> None:
628
752
  global default_enc, default_model, curr_cost
629
753
  # Generate a unique UUID for this client
630
754
  if not client_uuid:
631
755
  client_uuid = str(uuid.uuid4())
632
756
 
633
757
  # Create the WebSocket connection
634
- async with websockets.connect(f"{server_url}/{client_uuid}") as websocket:
635
- server_version = str(await websocket.recv())
758
+ with syncconnect(f"{server_url}/{client_uuid}") as websocket:
759
+ server_version = str(websocket.recv())
636
760
  print(f"Server version: {server_version}")
637
761
  client_version = importlib.metadata.version("wcgw")
638
- await websocket.send(client_version)
762
+ websocket.send(client_version)
639
763
 
640
764
  print(
641
765
  f"Connected. Share this user id with the chatbot: {client_uuid} \nLink: https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access"
@@ -643,24 +767,25 @@ async def register_client(server_url: str, client_uuid: str = "") -> None:
643
767
  try:
644
768
  while True:
645
769
  # Wait to receive data from the server
646
- message = await websocket.recv()
770
+ message = websocket.recv()
647
771
  mdata = Mdata.model_validate_json(message)
648
- with execution_lock:
649
- try:
650
- output, cost = get_tool_output(
651
- mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
652
- )
653
- curr_cost += cost
654
- print(f"{curr_cost=}")
655
- except Exception as e:
656
- output = f"GOT EXCEPTION while calling tool. Error: {e}"
657
- traceback.print_exc()
658
- assert isinstance(output, str)
659
- await websocket.send(output)
660
-
661
- except (websockets.ConnectionClosed, ConnectionError):
772
+ if isinstance(mdata.data, str):
773
+ raise Exception(mdata)
774
+ try:
775
+ output, cost = get_tool_output(
776
+ mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
777
+ )
778
+ curr_cost += cost
779
+ print(f"{curr_cost=}")
780
+ except Exception as e:
781
+ output = f"GOT EXCEPTION while calling tool. Error: {e}"
782
+ traceback.print_exc()
783
+ assert isinstance(output, str)
784
+ websocket.send(output)
785
+
786
+ except (websockets.ConnectionClosed, ConnectionError, OSError):
662
787
  print(f"Connection closed for UUID: {client_uuid}, retrying")
663
- await register_client(server_url, client_uuid)
788
+ register_client(server_url, client_uuid)
664
789
 
665
790
 
666
791
  run = Typer(pretty_exceptions_show_locals=False, no_args_is_help=True)
@@ -677,13 +802,4 @@ def app(
677
802
  print(f"wcgw version: {version_}")
678
803
  exit()
679
804
 
680
- thread1 = threading.Thread(target=execute_user_input)
681
- thread2 = threading.Thread(
682
- target=asyncio.run, args=(register_client(server_url, client_uuid or ""),)
683
- )
684
-
685
- thread1.start()
686
- thread2.start()
687
-
688
- thread1.join()
689
- thread2.join()
805
+ register_client(server_url, client_uuid or "")
wcgw/relay/serve.py CHANGED
@@ -17,7 +17,9 @@ from dotenv import load_dotenv
17
17
  from ..types_ import (
18
18
  BashCommand,
19
19
  BashInteraction,
20
+ CreateFileNew,
20
21
  FileEditFindReplace,
22
+ FullFileEdit,
21
23
  ResetShell,
22
24
  Writefile,
23
25
  Specials,
@@ -25,7 +27,16 @@ from ..types_ import (
25
27
 
26
28
 
27
29
  class Mdata(BaseModel):
28
- data: BashCommand | BashInteraction | Writefile | ResetShell | FileEditFindReplace
30
+ data: (
31
+ BashCommand
32
+ | BashInteraction
33
+ | Writefile
34
+ | CreateFileNew
35
+ | ResetShell
36
+ | FileEditFindReplace
37
+ | FullFileEdit
38
+ | str
39
+ )
29
40
  user_id: UUID
30
41
 
31
42
 
@@ -38,40 +49,7 @@ gpts: dict[UUID, Callable[[str], None]] = {}
38
49
  images: DefaultDict[UUID, dict[str, dict[str, Any]]] = DefaultDict(dict)
39
50
 
40
51
 
41
- @app.websocket("/register_serve_image/{uuid}")
42
- async def register_serve_image(websocket: WebSocket, uuid: UUID) -> None:
43
- raise Exception("Disabled")
44
- await websocket.accept()
45
- received_data = await websocket.receive_json()
46
- name = received_data["name"]
47
- image_b64 = received_data["image_b64"]
48
- image_bytes = base64.b64decode(image_b64)
49
- images[uuid][name] = {
50
- "content": image_bytes,
51
- "media_type": received_data["media_type"],
52
- }
53
-
54
-
55
- @app.get("/get_image/{uuid}/{name}")
56
- async def get_image(uuid: UUID, name: str) -> fastapi.responses.Response:
57
- return fastapi.responses.Response(
58
- content=images[uuid][name]["content"],
59
- media_type=images[uuid][name]["media_type"],
60
- )
61
-
62
-
63
- @app.websocket("/register/{uuid}")
64
- async def register_websocket_deprecated(websocket: WebSocket, uuid: UUID) -> None:
65
- await websocket.accept()
66
- await websocket.send_text(
67
- "Outdated client used. Deprecated api is being used. Upgrade the wcgw app."
68
- )
69
- await websocket.close(
70
- reason="This endpoint is deprecated. Please use /v1/register/{uuid}", code=1002
71
- )
72
-
73
-
74
- CLIENT_VERSION_MINIMUM = "1.0.0"
52
+ CLIENT_VERSION_MINIMUM = "1.1.0"
75
53
 
76
54
 
77
55
  @app.websocket("/v1/register/{uuid}")
@@ -88,7 +66,10 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
88
66
  sem_version_server = semantic_version.Version.coerce(CLIENT_VERSION_MINIMUM)
89
67
  if sem_version_client < sem_version_server:
90
68
  await websocket.send_text(
91
- f"Client version {client_version} is outdated. Please upgrade to {CLIENT_VERSION_MINIMUM} or higher."
69
+ Mdata(
70
+ user_id=uuid,
71
+ data=f"Client version {client_version} is outdated. Please upgrade to {CLIENT_VERSION_MINIMUM} or higher.",
72
+ ).model_dump_json()
92
73
  )
93
74
  await websocket.close(
94
75
  reason="Client version outdated. Please upgrade to the latest version.",
@@ -116,20 +97,12 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
116
97
  print(f"Client {uuid} disconnected")
117
98
 
118
99
 
119
- @app.post("/write_file")
120
- async def write_file_deprecated(write_file_data: Writefile, user_id: UUID) -> Response:
121
- return Response(
122
- content="This version of the API is deprecated. Please upgrade your client.",
123
- status_code=400,
124
- )
125
-
126
-
127
- class WritefileWithUUID(Writefile):
100
+ class CreateFileNewWithUUID(CreateFileNew):
128
101
  user_id: UUID
129
102
 
130
103
 
131
- @app.post("/v1/write_file")
132
- async def write_file(write_file_data: WritefileWithUUID) -> str:
104
+ @app.post("/v1/create_file")
105
+ async def create_file(write_file_data: CreateFileNewWithUUID) -> str:
133
106
  user_id = write_file_data.user_id
134
107
  if user_id not in clients:
135
108
  return "Failure: id not found, ask the user to check it."
@@ -153,13 +126,13 @@ async def write_file(write_file_data: WritefileWithUUID) -> str:
153
126
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
154
127
 
155
128
 
156
- class FileEditFindReplaceWithUUID(FileEditFindReplace):
129
+ class FullFileEditWithUUID(FullFileEdit):
157
130
  user_id: UUID
158
131
 
159
132
 
160
- @app.post("/v1/file_edit_find_replace")
133
+ @app.post("/v1/full_file_edit")
161
134
  async def file_edit_find_replace(
162
- file_edit_find_replace: FileEditFindReplaceWithUUID,
135
+ file_edit_find_replace: FullFileEditWithUUID,
163
136
  ) -> str:
164
137
  user_id = file_edit_find_replace.user_id
165
138
  if user_id not in clients:
@@ -218,14 +191,6 @@ async def reset_shell(reset_shell: ResetShellWithUUID) -> str:
218
191
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
219
192
 
220
193
 
221
- @app.post("/execute_bash")
222
- async def execute_bash_deprecated(excute_bash_data: Any, user_id: UUID) -> Response:
223
- return Response(
224
- content="This version of the API is deprecated. Please upgrade your client.",
225
- status_code=400,
226
- )
227
-
228
-
229
194
  class CommandWithUUID(BaseModel):
230
195
  command: str
231
196
  user_id: UUID
wcgw/types_.py CHANGED
@@ -16,6 +16,22 @@ class BashInteraction(BaseModel):
16
16
  send_specials: Optional[Sequence[Specials]] = None
17
17
  send_ascii: Optional[Sequence[int]] = None
18
18
 
19
+ def model_post_init(self, __context: object) -> None:
20
+ # Ensure only one of the fields is set
21
+ if (
22
+ sum(
23
+ [
24
+ int(bool(self.send_text)),
25
+ int(bool(self.send_specials)),
26
+ int(bool(self.send_ascii)),
27
+ ]
28
+ )
29
+ != 1
30
+ ):
31
+ raise ValueError(
32
+ "Exactly one of 'send_text', 'send_specials', or 'send_ascii' must be set"
33
+ )
34
+
19
35
 
20
36
  class ReadImage(BaseModel):
21
37
  file_path: str
@@ -27,6 +43,11 @@ class Writefile(BaseModel):
27
43
  file_content: str
28
44
 
29
45
 
46
+ class CreateFileNew(BaseModel):
47
+ file_path: str
48
+ file_content: str
49
+
50
+
30
51
  class FileEditFindReplace(BaseModel):
31
52
  file_path: str
32
53
  find_lines: str
@@ -35,3 +56,8 @@ class FileEditFindReplace(BaseModel):
35
56
 
36
57
  class ResetShell(BaseModel):
37
58
  should_reset: Literal[True] = True
59
+
60
+
61
+ class FullFileEdit(BaseModel):
62
+ file_path: str
63
+ file_edit_using_searh_replace_blocks: str
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 1.0.1
3
+ Version: 1.1.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>
7
7
  Requires-Python: <3.13,>=3.10
8
+ Requires-Dist: anthropic>=0.39.0
8
9
  Requires-Dist: fastapi>=0.115.0
9
10
  Requires-Dist: mypy>=1.11.2
10
11
  Requires-Dist: nltk>=3.9.1
@@ -0,0 +1,17 @@
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=ElFLxeEezLmIzmEmvts6BdtcWWjjK-R4VUS2BXA6qWw,24781
12
+ wcgw/relay/serve.py,sha256=hUMNnf4KxF-clUYvky09bWQtuRvqJny71D93PppSpec,7236
13
+ wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
14
+ wcgw-1.1.1.dist-info/METADATA,sha256=g91AStAolRmCDBXzhjKkTwljoHoJTv9uuS0iqGXpbHM,5255
15
+ wcgw-1.1.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
16
+ wcgw-1.1.1.dist-info/entry_points.txt,sha256=WlIB825-Vm9ZtNzgENQsbHj4DRMkbpVR7uSkQyBlaPA,93
17
+ wcgw-1.1.1.dist-info/RECORD,,