wcgw 1.0.0__py3-none-any.whl → 1.1.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/tools.py CHANGED
@@ -1,7 +1,10 @@
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
7
+ from pathlib import Path
5
8
  import re
6
9
  import sys
7
10
  import threading
@@ -13,6 +16,7 @@ from typing import (
13
16
  NewType,
14
17
  Optional,
15
18
  ParamSpec,
19
+ Type,
16
20
  TypeVar,
17
21
  TypedDict,
18
22
  )
@@ -41,7 +45,14 @@ from openai.types.chat import (
41
45
  ParsedChatCompletionMessage,
42
46
  )
43
47
  from nltk.metrics.distance import edit_distance
44
- from ..types_ import FileEditFindReplace, ResetShell, Writefile
48
+
49
+ from ..types_ import (
50
+ CreateFileNew,
51
+ FileEditFindReplace,
52
+ FullFileEdit,
53
+ ResetShell,
54
+ Writefile,
55
+ )
45
56
 
46
57
  from ..types_ import BashCommand
47
58
 
@@ -70,7 +81,10 @@ def render_terminal_output(text: str) -> str:
70
81
  break
71
82
  else:
72
83
  i = len(dsp)
73
- 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)
74
88
 
75
89
 
76
90
  class Confirmation(BaseModel):
@@ -368,8 +382,16 @@ def serve_image_in_bg(file_path: str, client_uuid: str, name: str) -> None:
368
382
  serve_image_in_bg(file_path, client_uuid, name)
369
383
 
370
384
 
385
+ MEDIA_TYPES = Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
386
+
387
+
371
388
  class ImageData(BaseModel):
372
- 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
373
395
 
374
396
 
375
397
  Param = ParamSpec("Param")
@@ -399,16 +421,25 @@ def read_image_from_shell(file_path: str) -> ImageData:
399
421
  image_bytes = image_file.read()
400
422
  image_b64 = base64.b64encode(image_bytes).decode("utf-8")
401
423
  image_type = mimetypes.guess_type(file_path)[0]
402
- return ImageData(dataurl=f"data:{image_type};base64,{image_b64}")
424
+ return ImageData(media_type=image_type, data=image_b64)
403
425
 
404
426
 
405
- def write_file(writefile: Writefile) -> str:
427
+ def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
406
428
  if not os.path.isabs(writefile.file_path):
407
- path_ = os.path.join(CWD, writefile.file_path)
429
+ return "Failure: file_path should be absolute path"
408
430
  else:
409
431
  path_ = writefile.file_path
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
+
438
+ path = Path(path_)
439
+ path.parent.mkdir(parents=True, exist_ok=True)
440
+
410
441
  try:
411
- with open(path_, "w") as f:
442
+ with path.open("w") as f:
412
443
  f.write(writefile.file_content)
413
444
  except OSError as e:
414
445
  return f"Error: {e}"
@@ -416,50 +447,128 @@ def write_file(writefile: Writefile) -> str:
416
447
  return "Success"
417
448
 
418
449
 
419
- def find_least_edit_distance_substring(content: str, find_str: str) -> str:
420
- 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
421
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
422
461
  # Slide window and find one with sum of edit distance least
423
462
  min_edit_distance = float("inf")
424
463
  min_edit_distance_lines = []
425
- 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)):
426
465
  edit_distance_sum = 0
427
466
  for j in range(len(find_lines)):
428
- 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])
429
471
  if edit_distance_sum < min_edit_distance:
430
472
  min_edit_distance = edit_distance_sum
431
- min_edit_distance_lines = content_lines[i : i + len(find_lines)]
432
- 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
+ )
433
492
 
493
+ content = content.replace(find_lines, replace_with_lines, 1)
494
+ return content
434
495
 
435
- def file_edit(file_edit: FileEditFindReplace) -> str:
436
- if not os.path.isabs(file_edit.file_path):
437
- path_ = os.path.join(CWD, file_edit.file_path)
496
+
497
+ def do_diff_edit(fedit: FullFileEdit) -> str:
498
+ console.log(f"Editing file: {fedit.file_path}")
499
+
500
+ if not os.path.isabs(fedit.file_path):
501
+ raise Exception("Failure: file_path should be absolute path")
438
502
  else:
439
- 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
440
540
 
441
- out_string = "\n".join("> " + line for line in file_edit.find_lines.split("\n"))
442
- in_string = "\n".join(
443
- "< " + line for line in file_edit.replace_with_lines.split("\n")
444
- )
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"))
445
561
  console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
446
562
  try:
447
563
  with open(path_) as f:
448
564
  content = f.read()
449
- # First find counts
450
- count = content.count(file_edit.find_lines)
451
565
 
452
- if count == 0:
453
- closest_match = find_least_edit_distance_substring(
454
- content, file_edit.find_lines
455
- )
456
- 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)
457
567
 
458
- content = content.replace(file_edit.find_lines, file_edit.replace_with_lines)
459
568
  with open(path_, "w") as f:
460
569
  f.write(content)
461
570
  except OSError as e:
462
- return f"Error: {e}"
571
+ raise Exception(f"Error: {e}")
463
572
  console.print(f"File written to {path_}")
464
573
  return "Success"
465
574
 
@@ -486,31 +595,53 @@ def take_help_of_ai_assistant(
486
595
  return output, cost
487
596
 
488
597
 
489
- def which_tool(args: str) -> BaseModel:
490
- adapter = TypeAdapter[
491
- Confirmation
492
- | BashCommand
493
- | BashInteraction
494
- | ResetShell
495
- | Writefile
496
- | FileEditFindReplace
497
- | AIAssistant
498
- | DoneFlag
499
- | ReadImage
500
- ](
501
- Confirmation
502
- | BashCommand
503
- | BashInteraction
504
- | ResetShell
505
- | Writefile
506
- | FileEditFindReplace
507
- | AIAssistant
508
- | DoneFlag
509
- | ReadImage
510
- )
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)
511
615
  return adapter.validate_python(json.loads(args))
512
616
 
513
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
+
514
645
  def get_tool_output(
515
646
  args: dict[object, object]
516
647
  | Confirmation
@@ -518,7 +649,9 @@ def get_tool_output(
518
649
  | BashInteraction
519
650
  | ResetShell
520
651
  | Writefile
652
+ | CreateFileNew
521
653
  | FileEditFindReplace
654
+ | FullFileEdit
522
655
  | AIAssistant
523
656
  | DoneFlag
524
657
  | ReadImage,
@@ -534,7 +667,9 @@ def get_tool_output(
534
667
  | BashInteraction
535
668
  | ResetShell
536
669
  | Writefile
670
+ | CreateFileNew
537
671
  | FileEditFindReplace
672
+ | FullFileEdit
538
673
  | AIAssistant
539
674
  | DoneFlag
540
675
  | ReadImage
@@ -544,7 +679,9 @@ def get_tool_output(
544
679
  | BashInteraction
545
680
  | ResetShell
546
681
  | Writefile
682
+ | CreateFileNew
547
683
  | FileEditFindReplace
684
+ | FullFileEdit
548
685
  | AIAssistant
549
686
  | DoneFlag
550
687
  | ReadImage
@@ -561,10 +698,16 @@ def get_tool_output(
561
698
  output = execute_bash(enc, arg, max_tokens)
562
699
  elif isinstance(arg, Writefile):
563
700
  console.print("Calling write file tool")
564
- 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
565
705
  elif isinstance(arg, FileEditFindReplace):
566
706
  console.print("Calling file edit tool")
567
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
568
711
  elif isinstance(arg, DoneFlag):
569
712
  console.print("Calling mark finish tool")
570
713
  output = mark_finish(arg), 0.0
@@ -593,44 +736,29 @@ curr_cost = 0.0
593
736
 
594
737
 
595
738
  class Mdata(BaseModel):
596
- data: BashCommand | BashInteraction | Writefile | ResetShell | FileEditFindReplace
597
-
598
-
599
- execution_lock = threading.Lock()
600
-
601
-
602
- def execute_user_input() -> None:
603
- while True:
604
- discard_input()
605
- user_input = input()
606
- with execution_lock:
607
- try:
608
- console.log(
609
- execute_bash(
610
- default_enc,
611
- BashInteraction(
612
- send_ascii=[ord(x) for x in user_input] + [ord("\n")]
613
- ),
614
- max_tokens=None,
615
- )[0]
616
- )
617
- except Exception as e:
618
- traceback.print_exc()
619
- console.log(f"Error: {e}")
739
+ data: (
740
+ BashCommand
741
+ | BashInteraction
742
+ | Writefile
743
+ | CreateFileNew
744
+ | ResetShell
745
+ | FileEditFindReplace
746
+ | FullFileEdit
747
+ )
620
748
 
621
749
 
622
- async def register_client(server_url: str, client_uuid: str = "") -> None:
750
+ def register_client(server_url: str, client_uuid: str = "") -> None:
623
751
  global default_enc, default_model, curr_cost
624
752
  # Generate a unique UUID for this client
625
753
  if not client_uuid:
626
754
  client_uuid = str(uuid.uuid4())
627
755
 
628
756
  # Create the WebSocket connection
629
- async with websockets.connect(f"{server_url}/{client_uuid}") as websocket:
630
- server_version = str(await websocket.recv())
757
+ with syncconnect(f"{server_url}/{client_uuid}") as websocket:
758
+ server_version = str(websocket.recv())
631
759
  print(f"Server version: {server_version}")
632
760
  client_version = importlib.metadata.version("wcgw")
633
- await websocket.send(client_version)
761
+ websocket.send(client_version)
634
762
 
635
763
  print(
636
764
  f"Connected. Share this user id with the chatbot: {client_uuid} \nLink: https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access"
@@ -638,24 +766,23 @@ async def register_client(server_url: str, client_uuid: str = "") -> None:
638
766
  try:
639
767
  while True:
640
768
  # Wait to receive data from the server
641
- message = await websocket.recv()
769
+ message = websocket.recv()
642
770
  mdata = Mdata.model_validate_json(message)
643
- with execution_lock:
644
- try:
645
- output, cost = get_tool_output(
646
- mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
647
- )
648
- curr_cost += cost
649
- print(f"{curr_cost=}")
650
- except Exception as e:
651
- output = f"GOT EXCEPTION while calling tool. Error: {e}"
652
- traceback.print_exc()
653
- assert isinstance(output, str)
654
- await websocket.send(output)
771
+ try:
772
+ output, cost = get_tool_output(
773
+ mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
774
+ )
775
+ curr_cost += cost
776
+ print(f"{curr_cost=}")
777
+ except Exception as e:
778
+ output = f"GOT EXCEPTION while calling tool. Error: {e}"
779
+ traceback.print_exc()
780
+ assert isinstance(output, str)
781
+ websocket.send(output)
655
782
 
656
783
  except (websockets.ConnectionClosed, ConnectionError):
657
784
  print(f"Connection closed for UUID: {client_uuid}, retrying")
658
- await register_client(server_url, client_uuid)
785
+ register_client(server_url, client_uuid)
659
786
 
660
787
 
661
788
  run = Typer(pretty_exceptions_show_locals=False, no_args_is_help=True)
@@ -672,13 +799,4 @@ def app(
672
799
  print(f"wcgw version: {version_}")
673
800
  exit()
674
801
 
675
- thread1 = threading.Thread(target=execute_user_input)
676
- thread2 = threading.Thread(
677
- target=asyncio.run, args=(register_client(server_url, client_uuid or ""),)
678
- )
679
-
680
- thread1.start()
681
- thread2.start()
682
-
683
- thread1.join()
684
- thread2.join()
802
+ 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,15 @@ 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
+ )
29
39
  user_id: UUID
30
40
 
31
41
 
@@ -38,39 +48,6 @@ gpts: dict[UUID, Callable[[str], None]] = {}
38
48
  images: DefaultDict[UUID, dict[str, dict[str, Any]]] = DefaultDict(dict)
39
49
 
40
50
 
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
51
  CLIENT_VERSION_MINIMUM = "1.0.0"
75
52
 
76
53
 
@@ -116,25 +93,15 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
116
93
  print(f"Client {uuid} disconnected")
117
94
 
118
95
 
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):
96
+ class CreateFileNewWithUUID(CreateFileNew):
128
97
  user_id: UUID
129
98
 
130
99
 
131
- @app.post("/v1/write_file")
132
- async def write_file(write_file_data: WritefileWithUUID) -> str:
100
+ @app.post("/v1/create_file")
101
+ async def create_file(write_file_data: CreateFileNewWithUUID) -> str:
133
102
  user_id = write_file_data.user_id
134
103
  if user_id not in clients:
135
- raise fastapi.HTTPException(
136
- status_code=404, detail="User with the provided id not found"
137
- )
104
+ return "Failure: id not found, ask the user to check it."
138
105
 
139
106
  results: Optional[str] = None
140
107
 
@@ -155,19 +122,17 @@ async def write_file(write_file_data: WritefileWithUUID) -> str:
155
122
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
156
123
 
157
124
 
158
- class FileEditFindReplaceWithUUID(FileEditFindReplace):
125
+ class FullFileEditWithUUID(FullFileEdit):
159
126
  user_id: UUID
160
127
 
161
128
 
162
- @app.post("/v1/file_edit_find_replace")
129
+ @app.post("/v1/full_file_edit")
163
130
  async def file_edit_find_replace(
164
- file_edit_find_replace: FileEditFindReplaceWithUUID,
131
+ file_edit_find_replace: FullFileEditWithUUID,
165
132
  ) -> str:
166
133
  user_id = file_edit_find_replace.user_id
167
134
  if user_id not in clients:
168
- raise fastapi.HTTPException(
169
- status_code=404, detail="User with the provided id not found"
170
- )
135
+ return "Failure: id not found, ask the user to check it."
171
136
 
172
137
  results: Optional[str] = None
173
138
 
@@ -201,9 +166,7 @@ class ResetShellWithUUID(ResetShell):
201
166
  async def reset_shell(reset_shell: ResetShellWithUUID) -> str:
202
167
  user_id = reset_shell.user_id
203
168
  if user_id not in clients:
204
- raise fastapi.HTTPException(
205
- status_code=404, detail="User with the provided id not found"
206
- )
169
+ return "Failure: id not found, ask the user to check it."
207
170
 
208
171
  results: Optional[str] = None
209
172
 
@@ -224,14 +187,6 @@ async def reset_shell(reset_shell: ResetShellWithUUID) -> str:
224
187
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
225
188
 
226
189
 
227
- @app.post("/execute_bash")
228
- async def execute_bash_deprecated(excute_bash_data: Any, user_id: UUID) -> Response:
229
- return Response(
230
- content="This version of the API is deprecated. Please upgrade your client.",
231
- status_code=400,
232
- )
233
-
234
-
235
190
  class CommandWithUUID(BaseModel):
236
191
  command: str
237
192
  user_id: UUID
@@ -241,9 +196,7 @@ class CommandWithUUID(BaseModel):
241
196
  async def bash_command(command: CommandWithUUID) -> str:
242
197
  user_id = command.user_id
243
198
  if user_id not in clients:
244
- raise fastapi.HTTPException(
245
- status_code=404, detail="User with the provided id not found"
246
- )
199
+ return "Failure: id not found, ask the user to check it."
247
200
 
248
201
  results: Optional[str] = None
249
202
 
@@ -274,9 +227,7 @@ class BashInteractionWithUUID(BashInteraction):
274
227
  async def bash_interaction(bash_interaction: BashInteractionWithUUID) -> str:
275
228
  user_id = bash_interaction.user_id
276
229
  if user_id not in clients:
277
- raise fastapi.HTTPException(
278
- status_code=404, detail="User with the provided id not found"
279
- )
230
+ return "Failure: id not found, ask the user to check it."
280
231
 
281
232
  results: Optional[str] = None
282
233
 
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