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/__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 +221 -105
- wcgw/relay/serve.py +23 -58
- wcgw/types_.py +26 -0
- {wcgw-1.0.1.dist-info → wcgw-1.1.1.dist-info}/METADATA +2 -1
- wcgw-1.1.1.dist-info/RECORD +17 -0
- {wcgw-1.0.1.dist-info → wcgw-1.1.1.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.1.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,30 @@ curr_cost = 0.0
|
|
|
598
736
|
|
|
599
737
|
|
|
600
738
|
class Mdata(BaseModel):
|
|
601
|
-
data:
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
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
|
-
|
|
635
|
-
server_version = str(
|
|
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
|
-
|
|
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 =
|
|
770
|
+
message = websocket.recv()
|
|
647
771
|
mdata = Mdata.model_validate_json(message)
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
)
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
132
|
-
async def
|
|
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
|
|
129
|
+
class FullFileEditWithUUID(FullFileEdit):
|
|
157
130
|
user_id: UUID
|
|
158
131
|
|
|
159
132
|
|
|
160
|
-
@app.post("/v1/
|
|
133
|
+
@app.post("/v1/full_file_edit")
|
|
161
134
|
async def file_edit_find_replace(
|
|
162
|
-
file_edit_find_replace:
|
|
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.
|
|
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,,
|