wcgw 1.3.0__py3-none-any.whl → 1.5.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 -0
- wcgw/client/__main__.py +2 -2
- wcgw/client/anthropic_client.py +83 -38
- wcgw/client/computer_use.py +416 -0
- wcgw/client/mcp_server/Readme.md +73 -0
- wcgw/client/mcp_server/__init__.py +11 -0
- wcgw/client/mcp_server/server.py +283 -0
- wcgw/client/openai_client.py +3 -3
- wcgw/client/sys_utils.py +40 -0
- wcgw/client/tools.py +184 -88
- wcgw/relay/serve.py +5 -12
- wcgw/types_.py +42 -6
- {wcgw-1.3.0.dist-info → wcgw-1.5.0.dist-info}/METADATA +75 -24
- wcgw-1.5.0.dist-info/RECORD +22 -0
- {wcgw-1.3.0.dist-info → wcgw-1.5.0.dist-info}/entry_points.txt +1 -0
- wcgw-1.3.0.dist-info/RECORD +0 -17
- {wcgw-1.3.0.dist-info → wcgw-1.5.0.dist-info}/WHEEL +0 -0
wcgw/client/tools.py
CHANGED
|
@@ -11,6 +11,7 @@ import threading
|
|
|
11
11
|
import importlib.metadata
|
|
12
12
|
import time
|
|
13
13
|
import traceback
|
|
14
|
+
from tempfile import TemporaryDirectory
|
|
14
15
|
from typing import (
|
|
15
16
|
Callable,
|
|
16
17
|
Literal,
|
|
@@ -24,6 +25,7 @@ from typing import (
|
|
|
24
25
|
import uuid
|
|
25
26
|
from pydantic import BaseModel, TypeAdapter
|
|
26
27
|
import typer
|
|
28
|
+
from .computer_use import run_computer_tool
|
|
27
29
|
from websockets.sync.client import connect as syncconnect
|
|
28
30
|
|
|
29
31
|
import os
|
|
@@ -57,11 +59,14 @@ from ..types_ import (
|
|
|
57
59
|
ReadFile,
|
|
58
60
|
ReadImage,
|
|
59
61
|
ResetShell,
|
|
60
|
-
|
|
62
|
+
Mouse,
|
|
63
|
+
Keyboard,
|
|
64
|
+
ScreenShot,
|
|
65
|
+
GetScreenInfo,
|
|
61
66
|
)
|
|
62
67
|
|
|
63
68
|
from .common import CostData, Models, discard_input
|
|
64
|
-
|
|
69
|
+
from .sys_utils import command_run
|
|
65
70
|
from .openai_utils import get_input_cost, get_output_cost
|
|
66
71
|
|
|
67
72
|
console = rich.console.Console(style="magenta", highlight=False, markup=False)
|
|
@@ -96,7 +101,8 @@ def ask_confirmation(prompt: Confirmation) -> str:
|
|
|
96
101
|
return "Yes" if response.lower() == "y" else "No"
|
|
97
102
|
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
PROMPT_CONST = "#@wcgw@#"
|
|
105
|
+
PROMPT = PROMPT_CONST
|
|
100
106
|
|
|
101
107
|
|
|
102
108
|
def start_shell() -> pexpect.spawn: # type: ignore
|
|
@@ -125,7 +131,7 @@ def _is_int(mystr: str) -> bool:
|
|
|
125
131
|
|
|
126
132
|
|
|
127
133
|
def _get_exit_code() -> int:
|
|
128
|
-
if PROMPT !=
|
|
134
|
+
if PROMPT != PROMPT_CONST:
|
|
129
135
|
return 0
|
|
130
136
|
# First reset the prompt in case venv was sourced or other reasons.
|
|
131
137
|
SHELL.sendline(f"export PS1={PROMPT}")
|
|
@@ -153,6 +159,7 @@ def _get_exit_code() -> int:
|
|
|
153
159
|
|
|
154
160
|
BASH_CLF_OUTPUT = Literal["repl", "pending"]
|
|
155
161
|
BASH_STATE: BASH_CLF_OUTPUT = "repl"
|
|
162
|
+
IS_IN_DOCKER: Optional[str] = ""
|
|
156
163
|
CWD = os.getcwd()
|
|
157
164
|
|
|
158
165
|
|
|
@@ -167,10 +174,11 @@ Current working directory: {CWD}
|
|
|
167
174
|
|
|
168
175
|
|
|
169
176
|
def reset_shell() -> str:
|
|
170
|
-
global SHELL, BASH_STATE, CWD
|
|
177
|
+
global SHELL, BASH_STATE, CWD, IS_IN_DOCKER
|
|
171
178
|
SHELL.close(True)
|
|
172
179
|
SHELL = start_shell()
|
|
173
180
|
BASH_STATE = "repl"
|
|
181
|
+
IS_IN_DOCKER = ""
|
|
174
182
|
CWD = os.getcwd()
|
|
175
183
|
return "Reset successful" + get_status()
|
|
176
184
|
|
|
@@ -234,6 +242,7 @@ def execute_bash(
|
|
|
234
242
|
enc: tiktoken.Encoding,
|
|
235
243
|
bash_arg: BashCommand | BashInteraction,
|
|
236
244
|
max_tokens: Optional[int],
|
|
245
|
+
timeout_s: Optional[float],
|
|
237
246
|
) -> tuple[str, float]:
|
|
238
247
|
global SHELL, BASH_STATE, CWD
|
|
239
248
|
try:
|
|
@@ -333,7 +342,7 @@ def execute_bash(
|
|
|
333
342
|
SHELL.expect(PROMPT)
|
|
334
343
|
return "---\n\nFailure: user interrupted the execution", 0.0
|
|
335
344
|
|
|
336
|
-
wait = 5
|
|
345
|
+
wait = timeout_s or 5
|
|
337
346
|
index = SHELL.expect([PROMPT, pexpect.TIMEOUT], timeout=wait)
|
|
338
347
|
if index == 1:
|
|
339
348
|
BASH_STATE = "pending"
|
|
@@ -438,35 +447,70 @@ def read_image_from_shell(file_path: str) -> ImageData:
|
|
|
438
447
|
if not os.path.isabs(file_path):
|
|
439
448
|
file_path = os.path.join(CWD, file_path)
|
|
440
449
|
|
|
441
|
-
if not
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
with open(file_path, "rb") as image_file:
|
|
445
|
-
image_bytes = image_file.read()
|
|
446
|
-
image_b64 = base64.b64encode(image_bytes).decode("utf-8")
|
|
447
|
-
image_type = mimetypes.guess_type(file_path)[0]
|
|
448
|
-
return ImageData(media_type=image_type, data=image_b64) # type: ignore
|
|
449
|
-
|
|
450
|
+
if not IS_IN_DOCKER:
|
|
451
|
+
if not os.path.exists(file_path):
|
|
452
|
+
raise ValueError(f"File {file_path} does not exist")
|
|
450
453
|
|
|
451
|
-
|
|
454
|
+
with open(file_path, "rb") as image_file:
|
|
455
|
+
image_bytes = image_file.read()
|
|
456
|
+
image_b64 = base64.b64encode(image_bytes).decode("utf-8")
|
|
457
|
+
image_type = mimetypes.guess_type(file_path)[0]
|
|
458
|
+
return ImageData(media_type=image_type, data=image_b64) # type: ignore
|
|
459
|
+
else:
|
|
460
|
+
with TemporaryDirectory() as tmpdir:
|
|
461
|
+
rcode = os.system(f"docker cp {IS_IN_DOCKER}:{file_path} {tmpdir}")
|
|
462
|
+
if rcode != 0:
|
|
463
|
+
raise Exception(f"Error: Read failed with code {rcode}")
|
|
464
|
+
path_ = os.path.join(tmpdir, os.path.basename(file_path))
|
|
465
|
+
with open(path_, "rb") as f:
|
|
466
|
+
image_bytes = f.read()
|
|
467
|
+
image_b64 = base64.b64encode(image_bytes).decode("utf-8")
|
|
468
|
+
image_type = mimetypes.guess_type(file_path)[0]
|
|
469
|
+
return ImageData(media_type=image_type, data=image_b64) # type: ignore
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
|
|
452
473
|
if not os.path.isabs(writefile.file_path):
|
|
453
474
|
return f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
454
475
|
else:
|
|
455
476
|
path_ = writefile.file_path
|
|
456
477
|
|
|
457
|
-
if
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
478
|
+
if not IS_IN_DOCKER:
|
|
479
|
+
if error_on_exist and os.path.exists(path_):
|
|
480
|
+
file_data = Path(path_).read_text()
|
|
481
|
+
if file_data:
|
|
482
|
+
return f"Error: can't write to existing file {path_}, use other functions to edit the file"
|
|
461
483
|
|
|
462
|
-
|
|
463
|
-
|
|
484
|
+
path = Path(path_)
|
|
485
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
486
|
+
|
|
487
|
+
try:
|
|
488
|
+
with path.open("w") as f:
|
|
489
|
+
f.write(writefile.file_content)
|
|
490
|
+
except OSError as e:
|
|
491
|
+
return f"Error: {e}"
|
|
492
|
+
else:
|
|
493
|
+
if error_on_exist:
|
|
494
|
+
# Check if it exists using os.system
|
|
495
|
+
cmd = f"test -f {path_}"
|
|
496
|
+
status = os.system(f'docker exec {IS_IN_DOCKER} bash -c "{cmd}"')
|
|
497
|
+
if status == 0:
|
|
498
|
+
return f"Error: can't write to existing file {path_}, use other functions to edit the file"
|
|
499
|
+
|
|
500
|
+
with TemporaryDirectory() as tmpdir:
|
|
501
|
+
tmppath = os.path.join(tmpdir, os.path.basename(path_))
|
|
502
|
+
with open(tmppath, "w") as f:
|
|
503
|
+
f.write(writefile.file_content)
|
|
504
|
+
os.chmod(tmppath, 0o777)
|
|
505
|
+
parent_dir = os.path.dirname(path_)
|
|
506
|
+
rcode = os.system(f"docker exec {IS_IN_DOCKER} mkdir -p {parent_dir}")
|
|
507
|
+
if rcode != 0:
|
|
508
|
+
return f"Error: Write failed with code while creating dirs {rcode}"
|
|
509
|
+
|
|
510
|
+
rcode = os.system(f"docker cp {tmppath} {IS_IN_DOCKER}:{path_}")
|
|
511
|
+
if rcode != 0:
|
|
512
|
+
return f"Error: Write failed with code {rcode}"
|
|
464
513
|
|
|
465
|
-
try:
|
|
466
|
-
with path.open("w") as f:
|
|
467
|
-
f.write(writefile.file_content)
|
|
468
|
-
except OSError as e:
|
|
469
|
-
return f"Error: {e}"
|
|
470
514
|
console.print(f"File written to {path_}")
|
|
471
515
|
return "Success"
|
|
472
516
|
|
|
@@ -545,11 +589,21 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
545
589
|
else:
|
|
546
590
|
path_ = fedit.file_path
|
|
547
591
|
|
|
548
|
-
if not
|
|
549
|
-
|
|
592
|
+
if not IS_IN_DOCKER:
|
|
593
|
+
if not os.path.exists(path_):
|
|
594
|
+
raise Exception(f"Error: file {path_} does not exist")
|
|
550
595
|
|
|
551
|
-
|
|
552
|
-
|
|
596
|
+
with open(path_) as f:
|
|
597
|
+
apply_diff_to = f.read()
|
|
598
|
+
else:
|
|
599
|
+
# Copy from docker
|
|
600
|
+
with TemporaryDirectory() as tmpdir:
|
|
601
|
+
rcode = os.system(f"docker cp {IS_IN_DOCKER}:{path_} {tmpdir}")
|
|
602
|
+
if rcode != 0:
|
|
603
|
+
raise Exception(f"Error: Read failed with code {rcode}")
|
|
604
|
+
path_tmp = os.path.join(tmpdir, os.path.basename(path_))
|
|
605
|
+
with open(path_tmp, "r") as f:
|
|
606
|
+
apply_diff_to = f.read()
|
|
553
607
|
|
|
554
608
|
fedit.file_edit_using_search_replace_blocks = (
|
|
555
609
|
fedit.file_edit_using_search_replace_blocks.strip()
|
|
@@ -597,40 +651,20 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
597
651
|
"Error: no valid search-replace blocks found, please check your syntax for FileEdit"
|
|
598
652
|
)
|
|
599
653
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
return "Success"
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
def file_edit(fedit: FileEditFindReplace) -> str:
|
|
607
|
-
if not os.path.isabs(fedit.file_path):
|
|
608
|
-
raise Exception(
|
|
609
|
-
f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
610
|
-
)
|
|
654
|
+
if not IS_IN_DOCKER:
|
|
655
|
+
with open(path_, "w") as f:
|
|
656
|
+
f.write(apply_diff_to)
|
|
611
657
|
else:
|
|
612
|
-
|
|
658
|
+
with TemporaryDirectory() as tmpdir:
|
|
659
|
+
path_tmp = os.path.join(tmpdir, os.path.basename(path_))
|
|
660
|
+
with open(path_tmp, "w") as f:
|
|
661
|
+
f.write(apply_diff_to)
|
|
662
|
+
os.chmod(path_tmp, 0o777)
|
|
663
|
+
# Copy to docker using docker cp
|
|
664
|
+
rcode = os.system(f"docker cp {path_tmp} {IS_IN_DOCKER}:{path_}")
|
|
665
|
+
if rcode != 0:
|
|
666
|
+
raise Exception(f"Error: Write failed with code {rcode}")
|
|
613
667
|
|
|
614
|
-
if not os.path.exists(path_):
|
|
615
|
-
raise Exception(f"Error: file {path_} does not exist")
|
|
616
|
-
|
|
617
|
-
if not fedit.find_lines:
|
|
618
|
-
raise Exception("Error: `find_lines` cannot be empty")
|
|
619
|
-
|
|
620
|
-
out_string = "\n".join("> " + line for line in fedit.find_lines.split("\n"))
|
|
621
|
-
in_string = "\n".join("< " + line for line in fedit.replace_with_lines.split("\n"))
|
|
622
|
-
console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
|
|
623
|
-
try:
|
|
624
|
-
with open(path_) as f:
|
|
625
|
-
content = f.read()
|
|
626
|
-
|
|
627
|
-
content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
|
|
628
|
-
|
|
629
|
-
with open(path_, "w") as f:
|
|
630
|
-
f.write(content)
|
|
631
|
-
except OSError as e:
|
|
632
|
-
raise Exception(f"Error: {e}")
|
|
633
|
-
console.print(f"File written to {path_}")
|
|
634
668
|
return "Success"
|
|
635
669
|
|
|
636
670
|
|
|
@@ -661,7 +695,6 @@ TOOLS = (
|
|
|
661
695
|
| BashCommand
|
|
662
696
|
| BashInteraction
|
|
663
697
|
| ResetShell
|
|
664
|
-
| Writefile
|
|
665
698
|
| CreateFileNew
|
|
666
699
|
| FileEditFindReplace
|
|
667
700
|
| FileEdit
|
|
@@ -670,6 +703,10 @@ TOOLS = (
|
|
|
670
703
|
| ReadImage
|
|
671
704
|
| ReadFile
|
|
672
705
|
| Initialize
|
|
706
|
+
| Mouse
|
|
707
|
+
| Keyboard
|
|
708
|
+
| ScreenShot
|
|
709
|
+
| GetScreenInfo
|
|
673
710
|
)
|
|
674
711
|
|
|
675
712
|
|
|
@@ -687,8 +724,6 @@ def which_tool_name(name: str) -> Type[TOOLS]:
|
|
|
687
724
|
return BashInteraction
|
|
688
725
|
elif name == "ResetShell":
|
|
689
726
|
return ResetShell
|
|
690
|
-
elif name == "Writefile":
|
|
691
|
-
return Writefile
|
|
692
727
|
elif name == "CreateFileNew":
|
|
693
728
|
return CreateFileNew
|
|
694
729
|
elif name == "FileEditFindReplace":
|
|
@@ -705,6 +740,14 @@ def which_tool_name(name: str) -> Type[TOOLS]:
|
|
|
705
740
|
return ReadFile
|
|
706
741
|
elif name == "Initialize":
|
|
707
742
|
return Initialize
|
|
743
|
+
elif name == "Mouse":
|
|
744
|
+
return Mouse
|
|
745
|
+
elif name == "Keyboard":
|
|
746
|
+
return Keyboard
|
|
747
|
+
elif name == "ScreenShot":
|
|
748
|
+
return ScreenShot
|
|
749
|
+
elif name == "GetScreenInfo":
|
|
750
|
+
return GetScreenInfo
|
|
708
751
|
else:
|
|
709
752
|
raise ValueError(f"Unknown tool name: {name}")
|
|
710
753
|
|
|
@@ -715,7 +758,6 @@ def get_tool_output(
|
|
|
715
758
|
| BashCommand
|
|
716
759
|
| BashInteraction
|
|
717
760
|
| ResetShell
|
|
718
|
-
| Writefile
|
|
719
761
|
| CreateFileNew
|
|
720
762
|
| FileEditFindReplace
|
|
721
763
|
| FileEdit
|
|
@@ -723,19 +765,23 @@ def get_tool_output(
|
|
|
723
765
|
| DoneFlag
|
|
724
766
|
| ReadImage
|
|
725
767
|
| Initialize
|
|
726
|
-
| ReadFile
|
|
768
|
+
| ReadFile
|
|
769
|
+
| Mouse
|
|
770
|
+
| Keyboard
|
|
771
|
+
| ScreenShot
|
|
772
|
+
| GetScreenInfo,
|
|
727
773
|
enc: tiktoken.Encoding,
|
|
728
774
|
limit: float,
|
|
729
775
|
loop_call: Callable[[str, float], tuple[str, float]],
|
|
730
776
|
max_tokens: Optional[int],
|
|
731
|
-
) -> tuple[str | ImageData | DoneFlag, float]:
|
|
777
|
+
) -> tuple[list[str | ImageData | DoneFlag], float]:
|
|
778
|
+
global IS_IN_DOCKER
|
|
732
779
|
if isinstance(args, dict):
|
|
733
780
|
adapter = TypeAdapter[
|
|
734
781
|
Confirmation
|
|
735
782
|
| BashCommand
|
|
736
783
|
| BashInteraction
|
|
737
784
|
| ResetShell
|
|
738
|
-
| Writefile
|
|
739
785
|
| CreateFileNew
|
|
740
786
|
| FileEditFindReplace
|
|
741
787
|
| FileEdit
|
|
@@ -744,12 +790,15 @@ def get_tool_output(
|
|
|
744
790
|
| ReadImage
|
|
745
791
|
| ReadFile
|
|
746
792
|
| Initialize
|
|
793
|
+
| Mouse
|
|
794
|
+
| Keyboard
|
|
795
|
+
| ScreenShot
|
|
796
|
+
| GetScreenInfo,
|
|
747
797
|
](
|
|
748
798
|
Confirmation
|
|
749
799
|
| BashCommand
|
|
750
800
|
| BashInteraction
|
|
751
801
|
| ResetShell
|
|
752
|
-
| Writefile
|
|
753
802
|
| CreateFileNew
|
|
754
803
|
| FileEditFindReplace
|
|
755
804
|
| FileEdit
|
|
@@ -758,6 +807,10 @@ def get_tool_output(
|
|
|
758
807
|
| ReadImage
|
|
759
808
|
| ReadFile
|
|
760
809
|
| Initialize
|
|
810
|
+
| Mouse
|
|
811
|
+
| Keyboard
|
|
812
|
+
| ScreenShot
|
|
813
|
+
| GetScreenInfo
|
|
761
814
|
)
|
|
762
815
|
arg = adapter.validate_python(args)
|
|
763
816
|
else:
|
|
@@ -768,16 +821,10 @@ def get_tool_output(
|
|
|
768
821
|
output = ask_confirmation(arg), 0.0
|
|
769
822
|
elif isinstance(arg, (BashCommand | BashInteraction)):
|
|
770
823
|
console.print("Calling execute bash tool")
|
|
771
|
-
output = execute_bash(enc, arg, max_tokens)
|
|
772
|
-
elif isinstance(arg, Writefile):
|
|
773
|
-
console.print("Calling write file tool")
|
|
774
|
-
output = write_file(arg, False), 0
|
|
824
|
+
output = execute_bash(enc, arg, max_tokens, None)
|
|
775
825
|
elif isinstance(arg, CreateFileNew):
|
|
776
826
|
console.print("Calling write file tool")
|
|
777
827
|
output = write_file(arg, True), 0
|
|
778
|
-
elif isinstance(arg, FileEditFindReplace):
|
|
779
|
-
console.print("Calling file edit tool")
|
|
780
|
-
output = file_edit(arg), 0.0
|
|
781
828
|
elif isinstance(arg, FileEdit):
|
|
782
829
|
console.print("Calling full file edit tool")
|
|
783
830
|
output = do_diff_edit(arg), 0.0
|
|
@@ -799,11 +846,50 @@ def get_tool_output(
|
|
|
799
846
|
elif isinstance(arg, Initialize):
|
|
800
847
|
console.print("Calling initial info tool")
|
|
801
848
|
output = initial_info(), 0.0
|
|
849
|
+
elif isinstance(arg, (Mouse, Keyboard, ScreenShot, GetScreenInfo)):
|
|
850
|
+
console.print(f"Calling {type(arg).__name__} tool")
|
|
851
|
+
outputs_cost = run_computer_tool(arg), 0.0
|
|
852
|
+
console.print(outputs_cost[0][0])
|
|
853
|
+
outputs: list[ImageData | str | DoneFlag] = [outputs_cost[0][0]]
|
|
854
|
+
imgBs64 = outputs_cost[0][1]
|
|
855
|
+
if imgBs64:
|
|
856
|
+
console.print("Captured screenshot")
|
|
857
|
+
outputs.append(ImageData(media_type="image/png", data=imgBs64))
|
|
858
|
+
if not IS_IN_DOCKER:
|
|
859
|
+
try:
|
|
860
|
+
# At this point we should go into the docker env
|
|
861
|
+
res, _ = execute_bash(
|
|
862
|
+
enc,
|
|
863
|
+
BashCommand(
|
|
864
|
+
command=f"docker exec -it {arg.docker_image_id} sh"
|
|
865
|
+
),
|
|
866
|
+
None,
|
|
867
|
+
0.2,
|
|
868
|
+
)
|
|
869
|
+
# At this point we should go into the docker env
|
|
870
|
+
res, _ = execute_bash(
|
|
871
|
+
enc,
|
|
872
|
+
BashInteraction(
|
|
873
|
+
send_text=f"export PS1={PROMPT}", type="BashInteraction"
|
|
874
|
+
),
|
|
875
|
+
None,
|
|
876
|
+
0.2,
|
|
877
|
+
)
|
|
878
|
+
# Do chown of home dir
|
|
879
|
+
except Exception as e:
|
|
880
|
+
reset_shell()
|
|
881
|
+
raise Exception(
|
|
882
|
+
f"Some error happened while going inside docker. I've reset the shell. Please start again. Error {e}"
|
|
883
|
+
)
|
|
884
|
+
IS_IN_DOCKER = arg.docker_image_id
|
|
885
|
+
return outputs, outputs_cost[1]
|
|
802
886
|
else:
|
|
803
887
|
raise ValueError(f"Unknown tool: {arg}")
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
888
|
+
if isinstance(output[0], str):
|
|
889
|
+
console.print(str(output[0]))
|
|
890
|
+
else:
|
|
891
|
+
console.print(f"Received {type(output[0])} from tool")
|
|
892
|
+
return [output[0]], output[1]
|
|
807
893
|
|
|
808
894
|
|
|
809
895
|
History = list[ChatCompletionMessageParam]
|
|
@@ -818,7 +904,6 @@ class Mdata(BaseModel):
|
|
|
818
904
|
data: (
|
|
819
905
|
BashCommand
|
|
820
906
|
| BashInteraction
|
|
821
|
-
| Writefile
|
|
822
907
|
| CreateFileNew
|
|
823
908
|
| ResetShell
|
|
824
909
|
| FileEditFindReplace
|
|
@@ -853,9 +938,10 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
|
|
|
853
938
|
if isinstance(mdata.data, str):
|
|
854
939
|
raise Exception(mdata)
|
|
855
940
|
try:
|
|
856
|
-
|
|
941
|
+
outputs, cost = get_tool_output(
|
|
857
942
|
mdata.data, default_enc, 0.0, lambda x, y: ("", 0), 8000
|
|
858
943
|
)
|
|
944
|
+
output = outputs[0]
|
|
859
945
|
curr_cost += cost
|
|
860
946
|
print(f"{curr_cost=}")
|
|
861
947
|
except Exception as e:
|
|
@@ -893,12 +979,22 @@ def read_file(readfile: ReadFile, max_tokens: Optional[int]) -> str:
|
|
|
893
979
|
if not os.path.isabs(readfile.file_path):
|
|
894
980
|
return f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
895
981
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
982
|
+
if not IS_IN_DOCKER:
|
|
983
|
+
path = Path(readfile.file_path)
|
|
984
|
+
if not path.exists():
|
|
985
|
+
return f"Error: file {readfile.file_path} does not exist"
|
|
899
986
|
|
|
900
|
-
|
|
901
|
-
|
|
987
|
+
with path.open("r") as f:
|
|
988
|
+
content = f.read()
|
|
989
|
+
|
|
990
|
+
else:
|
|
991
|
+
return_code, content, stderr = command_run(
|
|
992
|
+
f"cat {readfile.file_path}",
|
|
993
|
+
)
|
|
994
|
+
if return_code != 0:
|
|
995
|
+
raise Exception(
|
|
996
|
+
f"Error: cat {readfile.file_path} failed with code {return_code}\nstdout: {content}\nstderr: {stderr}"
|
|
997
|
+
)
|
|
902
998
|
|
|
903
999
|
if max_tokens is not None:
|
|
904
1000
|
tokens = default_enc.encode(content)
|
wcgw/relay/serve.py
CHANGED
|
@@ -23,7 +23,6 @@ from ..types_ import (
|
|
|
23
23
|
Initialize,
|
|
24
24
|
ReadFile,
|
|
25
25
|
ResetShell,
|
|
26
|
-
Writefile,
|
|
27
26
|
Specials,
|
|
28
27
|
)
|
|
29
28
|
|
|
@@ -32,7 +31,6 @@ class Mdata(BaseModel):
|
|
|
32
31
|
data: (
|
|
33
32
|
BashCommand
|
|
34
33
|
| BashInteraction
|
|
35
|
-
| Writefile
|
|
36
34
|
| CreateFileNew
|
|
37
35
|
| ResetShell
|
|
38
36
|
| FileEditFindReplace
|
|
@@ -67,8 +65,7 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
|
|
|
67
65
|
# receive client version
|
|
68
66
|
client_version = await websocket.receive_text()
|
|
69
67
|
sem_version_client = semantic_version.Version.coerce(client_version)
|
|
70
|
-
sem_version_server = semantic_version.Version.coerce(
|
|
71
|
-
CLIENT_VERSION_MINIMUM)
|
|
68
|
+
sem_version_server = semantic_version.Version.coerce(CLIENT_VERSION_MINIMUM)
|
|
72
69
|
if sem_version_client < sem_version_server:
|
|
73
70
|
await websocket.send_text(
|
|
74
71
|
Mdata(
|
|
@@ -93,8 +90,7 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
|
|
|
93
90
|
while True:
|
|
94
91
|
received_data = await websocket.receive_text()
|
|
95
92
|
if uuid not in gpts:
|
|
96
|
-
raise fastapi.HTTPException(
|
|
97
|
-
status_code=400, detail="No call made")
|
|
93
|
+
raise fastapi.HTTPException(status_code=400, detail="No call made")
|
|
98
94
|
gpts[uuid](received_data)
|
|
99
95
|
except WebSocketDisconnect:
|
|
100
96
|
# Remove the client if the WebSocket is disconnected
|
|
@@ -281,9 +277,7 @@ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
|
|
|
281
277
|
|
|
282
278
|
gpts[user_id] = put_results
|
|
283
279
|
|
|
284
|
-
await clients[user_id](
|
|
285
|
-
Mdata(data=read_file_data, user_id=user_id)
|
|
286
|
-
)
|
|
280
|
+
await clients[user_id](Mdata(data=read_file_data, user_id=user_id))
|
|
287
281
|
|
|
288
282
|
start_time = time.time()
|
|
289
283
|
while time.time() - start_time < 30:
|
|
@@ -293,6 +287,7 @@ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
|
|
|
293
287
|
|
|
294
288
|
raise fastapi.HTTPException(status_code=500, detail="Timeout error")
|
|
295
289
|
|
|
290
|
+
|
|
296
291
|
class InitializeWithUUID(Initialize):
|
|
297
292
|
user_id: UUID
|
|
298
293
|
|
|
@@ -311,9 +306,7 @@ async def initialize(initialize_data: InitializeWithUUID) -> str:
|
|
|
311
306
|
|
|
312
307
|
gpts[user_id] = put_results
|
|
313
308
|
|
|
314
|
-
await clients[user_id](
|
|
315
|
-
Mdata(data=initialize_data, user_id=user_id)
|
|
316
|
-
)
|
|
309
|
+
await clients[user_id](Mdata(data=initialize_data, user_id=user_id))
|
|
317
310
|
|
|
318
311
|
start_time = time.time()
|
|
319
312
|
while time.time() - start_time < 30:
|
wcgw/types_.py
CHANGED
|
@@ -24,11 +24,6 @@ class ReadImage(BaseModel):
|
|
|
24
24
|
type: Literal["ReadImage"]
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class Writefile(BaseModel):
|
|
28
|
-
file_path: str
|
|
29
|
-
file_content: str
|
|
30
|
-
|
|
31
|
-
|
|
32
27
|
class CreateFileNew(BaseModel):
|
|
33
28
|
file_path: str
|
|
34
29
|
file_content: str
|
|
@@ -55,4 +50,45 @@ class FileEdit(BaseModel):
|
|
|
55
50
|
|
|
56
51
|
|
|
57
52
|
class Initialize(BaseModel):
|
|
58
|
-
type: Literal["Initialize"]
|
|
53
|
+
type: Literal["Initialize"]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GetScreenInfo(BaseModel):
|
|
57
|
+
type: Literal["GetScreenInfo"]
|
|
58
|
+
docker_image_id: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ScreenShot(BaseModel):
|
|
62
|
+
type: Literal["ScreenShot"]
|
|
63
|
+
docker_image_id: str
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MouseMove(BaseModel):
|
|
67
|
+
x: int
|
|
68
|
+
y: int
|
|
69
|
+
type: Literal["MouseMove"]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LeftClickDrag(BaseModel):
|
|
73
|
+
x: int
|
|
74
|
+
y: int
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class MouseButton(BaseModel):
|
|
78
|
+
button_type: Literal[
|
|
79
|
+
"left_click",
|
|
80
|
+
"right_click",
|
|
81
|
+
"middle_click",
|
|
82
|
+
"double_click",
|
|
83
|
+
"scroll_up",
|
|
84
|
+
"scroll_down",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Mouse(BaseModel):
|
|
89
|
+
action: MouseButton | LeftClickDrag | MouseMove
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Keyboard(BaseModel):
|
|
93
|
+
action: Literal["key", "type"]
|
|
94
|
+
text: str
|