wcgw 1.0.1__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/__init__.py +1 -1
- wcgw/client/anthropic_client.py +415 -0
- wcgw/client/cli.py +40 -0
- wcgw/client/diff-instructions.txt +44 -0
- wcgw/client/{basic.py → openai_client.py} +41 -13
- wcgw/client/tools.py +216 -103
- wcgw/relay/serve.py +17 -56
- wcgw/types_.py +26 -0
- {wcgw-1.0.1.dist-info → wcgw-1.1.0.dist-info}/METADATA +2 -1
- wcgw-1.1.0.dist-info/RECORD +17 -0
- {wcgw-1.0.1.dist-info → wcgw-1.1.0.dist-info}/WHEEL +1 -1
- wcgw/client/claude.py +0 -384
- wcgw-1.0.1.dist-info/RECORD +0 -15
- /wcgw/client/{openai_adapters.py → __init__.py} +0 -0
- {wcgw-1.0.1.dist-info → wcgw-1.1.0.dist-info}/entry_points.txt +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
425
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
441
|
-
|
|
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_ =
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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,29 @@ curr_cost = 0.0
|
|
|
598
736
|
|
|
599
737
|
|
|
600
738
|
class Mdata(BaseModel):
|
|
601
|
-
data:
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
+
)
|
|
625
748
|
|
|
626
749
|
|
|
627
|
-
|
|
750
|
+
def register_client(server_url: str, client_uuid: str = "") -> None:
|
|
628
751
|
global default_enc, default_model, curr_cost
|
|
629
752
|
# Generate a unique UUID for this client
|
|
630
753
|
if not client_uuid:
|
|
631
754
|
client_uuid = str(uuid.uuid4())
|
|
632
755
|
|
|
633
756
|
# Create the WebSocket connection
|
|
634
|
-
|
|
635
|
-
server_version = str(
|
|
757
|
+
with syncconnect(f"{server_url}/{client_uuid}") as websocket:
|
|
758
|
+
server_version = str(websocket.recv())
|
|
636
759
|
print(f"Server version: {server_version}")
|
|
637
760
|
client_version = importlib.metadata.version("wcgw")
|
|
638
|
-
|
|
761
|
+
websocket.send(client_version)
|
|
639
762
|
|
|
640
763
|
print(
|
|
641
764
|
f"Connected. Share this user id with the chatbot: {client_uuid} \nLink: https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access"
|
|
@@ -643,24 +766,23 @@ async def register_client(server_url: str, client_uuid: str = "") -> None:
|
|
|
643
766
|
try:
|
|
644
767
|
while True:
|
|
645
768
|
# Wait to receive data from the server
|
|
646
|
-
message =
|
|
769
|
+
message = websocket.recv()
|
|
647
770
|
mdata = Mdata.model_validate_json(message)
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
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)
|
|
660
782
|
|
|
661
783
|
except (websockets.ConnectionClosed, ConnectionError):
|
|
662
784
|
print(f"Connection closed for UUID: {client_uuid}, retrying")
|
|
663
|
-
|
|
785
|
+
register_client(server_url, client_uuid)
|
|
664
786
|
|
|
665
787
|
|
|
666
788
|
run = Typer(pretty_exceptions_show_locals=False, no_args_is_help=True)
|
|
@@ -677,13 +799,4 @@ def app(
|
|
|
677
799
|
print(f"wcgw version: {version_}")
|
|
678
800
|
exit()
|
|
679
801
|
|
|
680
|
-
|
|
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()
|
|
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:
|
|
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,20 +93,12 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
|
|
|
116
93
|
print(f"Client {uuid} disconnected")
|
|
117
94
|
|
|
118
95
|
|
|
119
|
-
|
|
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/
|
|
132
|
-
async def
|
|
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
104
|
return "Failure: id not found, ask the user to check it."
|
|
@@ -153,13 +122,13 @@ async def write_file(write_file_data: WritefileWithUUID) -> str:
|
|
|
153
122
|
raise fastapi.HTTPException(status_code=500, detail="Timeout error")
|
|
154
123
|
|
|
155
124
|
|
|
156
|
-
class
|
|
125
|
+
class FullFileEditWithUUID(FullFileEdit):
|
|
157
126
|
user_id: UUID
|
|
158
127
|
|
|
159
128
|
|
|
160
|
-
@app.post("/v1/
|
|
129
|
+
@app.post("/v1/full_file_edit")
|
|
161
130
|
async def file_edit_find_replace(
|
|
162
|
-
file_edit_find_replace:
|
|
131
|
+
file_edit_find_replace: FullFileEditWithUUID,
|
|
163
132
|
) -> str:
|
|
164
133
|
user_id = file_edit_find_replace.user_id
|
|
165
134
|
if user_id not in clients:
|
|
@@ -218,14 +187,6 @@ async def reset_shell(reset_shell: ResetShellWithUUID) -> str:
|
|
|
218
187
|
raise fastapi.HTTPException(status_code=500, detail="Timeout error")
|
|
219
188
|
|
|
220
189
|
|
|
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
190
|
class CommandWithUUID(BaseModel):
|
|
230
191
|
command: str
|
|
231
192
|
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
|
|
3
|
+
Version: 1.1.0
|
|
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=KxMd8aPJmnkH-R-IisEAJ6KHAX95PRt2kLh9Z1t28hE,24667
|
|
12
|
+
wcgw/relay/serve.py,sha256=Ch-vdUK-W60fkVVwFZIfy2L1VT7Te4Yt4Z8Bb0ykAh8,7131
|
|
13
|
+
wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
|
|
14
|
+
wcgw-1.1.0.dist-info/METADATA,sha256=uvRAnlHl03aoR4uhhF0qVTK3Et3GF6DHIofx1jHUNQQ,5255
|
|
15
|
+
wcgw-1.1.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
16
|
+
wcgw-1.1.0.dist-info/entry_points.txt,sha256=WlIB825-Vm9ZtNzgENQsbHj4DRMkbpVR7uSkQyBlaPA,93
|
|
17
|
+
wcgw-1.1.0.dist-info/RECORD,,
|