wcgw 2.4.2__py3-none-any.whl → 2.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.

@@ -29,7 +29,7 @@ from ..types_ import (
29
29
  FileEdit,
30
30
  Keyboard,
31
31
  Mouse,
32
- ReadFile,
32
+ ReadFiles,
33
33
  ReadImage,
34
34
  ResetShell,
35
35
  ScreenShot,
@@ -41,12 +41,7 @@ from .common import CostData
41
41
  from .tools import ImageData
42
42
  from .computer_use import Computer
43
43
 
44
- from .tools import (
45
- DoneFlag,
46
- get_tool_output,
47
- which_tool_name,
48
- )
49
- import tiktoken
44
+ from .tools import DoneFlag, get_tool_output, which_tool_name, default_enc
50
45
 
51
46
  from urllib import parse
52
47
  import subprocess
@@ -156,10 +151,6 @@ def loop(
156
151
 
157
152
  limit = 1
158
153
 
159
- enc = tiktoken.encoding_for_model(
160
- "gpt-4o-2024-08-06",
161
- )
162
-
163
154
  tools = [
164
155
  ToolParam(
165
156
  input_schema=BashCommand.model_json_schema(),
@@ -192,12 +183,11 @@ def loop(
192
183
  """,
193
184
  ),
194
185
  ToolParam(
195
- input_schema=ReadFile.model_json_schema(),
196
- name="ReadFile",
186
+ input_schema=ReadFiles.model_json_schema(),
187
+ name="ReadFiles",
197
188
  description="""
198
- - Read full file content
199
- - Provide absolute file path only
200
- - Use this instead of 'cat' from BashCommand
189
+ - Read full file content of one or more files.
190
+ - Provide absolute file paths only
201
191
  """,
202
192
  ),
203
193
  ToolParam(
@@ -451,7 +441,7 @@ System information:
451
441
  try:
452
442
  output_or_dones, _ = get_tool_output(
453
443
  tool_parsed,
454
- enc,
444
+ default_enc,
455
445
  limit - cost,
456
446
  loop,
457
447
  max_tokens=8000,
wcgw/client/common.py CHANGED
@@ -38,7 +38,9 @@ def discard_input() -> None:
38
38
  while True:
39
39
  # Check if there is input to be read
40
40
  if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
41
- sys.stdin.read(1) # Read one character at a time to flush the input buffer
41
+ sys.stdin.read(
42
+ 1
43
+ ) # Read one character at a time to flush the input buffer
42
44
  else:
43
45
  break
44
46
  finally:
@@ -21,7 +21,7 @@ from ...types_ import (
21
21
  FileEdit,
22
22
  Keyboard,
23
23
  Mouse,
24
- ReadFile,
24
+ ReadFiles,
25
25
  ReadImage,
26
26
  ResetShell,
27
27
  Initialize,
@@ -76,7 +76,7 @@ async def handle_list_tools() -> list[types.Tool]:
76
76
  inputSchema=Initialize.model_json_schema(),
77
77
  name="Initialize",
78
78
  description="""
79
- - Always call this at the start of the conversation before anything else.
79
+ - Always call this at the start of the conversation before using any of the shell tools from wcgw.
80
80
  """,
81
81
  ),
82
82
  ToolParam(
@@ -92,6 +92,7 @@ async def handle_list_tools() -> list[types.Tool]:
92
92
  - The control will return to you in {SLEEP_TIME_MAX_S} seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
93
93
  - Run long running commands in background using screen instead of "&".
94
94
  - Use longer wait_for_seconds if the command is expected to run for a long time.
95
+ - Do not use 'cat' to read files, use ReadFiles tool instead.
95
96
  """,
96
97
  ),
97
98
  ToolParam(
@@ -110,12 +111,11 @@ async def handle_list_tools() -> list[types.Tool]:
110
111
  """,
111
112
  ),
112
113
  ToolParam(
113
- inputSchema=ReadFile.model_json_schema(),
114
- name="ReadFile",
114
+ inputSchema=ReadFiles.model_json_schema(),
115
+ name="ReadFiles",
115
116
  description="""
116
- - Read full file content
117
- - Provide absolute file path only
118
- - Use this instead of 'cat' from BashCommand
117
+ - Read full file content of one or more files.
118
+ - Provide absolute file paths only
119
119
  """,
120
120
  ),
121
121
  ToolParam(
@@ -17,6 +17,7 @@ from openai.types.chat import (
17
17
  )
18
18
  import rich
19
19
  import petname # type: ignore[import-untyped]
20
+ import tokenizers # type: ignore[import-untyped]
20
21
  from typer import Typer
21
22
  import uuid
22
23
 
@@ -26,7 +27,7 @@ from ..types_ import (
26
27
  WriteIfEmpty,
27
28
  FileEdit,
28
29
  ReadImage,
29
- ReadFile,
30
+ ReadFiles,
30
31
  ResetShell,
31
32
  )
32
33
 
@@ -40,7 +41,6 @@ from .tools import (
40
41
  get_tool_output,
41
42
  which_tool,
42
43
  )
43
- import tiktoken
44
44
 
45
45
  from urllib import parse
46
46
  import subprocess
@@ -160,9 +160,7 @@ def loop(
160
160
  config.cost_limit = limit
161
161
  limit = config.cost_limit
162
162
 
163
- enc = tiktoken.encoding_for_model(
164
- config.model if not config.model.startswith("o1") else "gpt-4o"
165
- )
163
+ enc = tokenizers.Tokenizer.from_pretrained("Xenova/gpt-4o")
166
164
 
167
165
  tools = [
168
166
  openai.pydantic_function_tool(
@@ -188,11 +186,10 @@ def loop(
188
186
  - Only one of send_text, send_specials, send_ascii should be provided.""",
189
187
  ),
190
188
  openai.pydantic_function_tool(
191
- ReadFile,
189
+ ReadFiles,
192
190
  description="""
193
- - Read full file content
194
- - Provide absolute file path only
195
- - Use this instead of 'cat' from BashCommand
191
+ - Read full file content of one or more files.
192
+ - Provide absolute file paths only
196
193
  """,
197
194
  ),
198
195
  openai.pydantic_function_tool(
@@ -15,7 +15,7 @@ from openai.types.chat import (
15
15
  ParsedChatCompletionMessage,
16
16
  )
17
17
  import rich
18
- import tiktoken
18
+ from tokenizers import Tokenizer # type: ignore[import-untyped]
19
19
  from typer import Typer
20
20
  import uuid
21
21
 
@@ -23,7 +23,7 @@ from .common import CostData, History
23
23
 
24
24
 
25
25
  def get_input_cost(
26
- cost_map: CostData, enc: tiktoken.Encoding, history: History
26
+ cost_map: CostData, enc: Tokenizer, history: History
27
27
  ) -> tuple[float, int]:
28
28
  input_tokens = 0
29
29
  for msg in history:
@@ -31,8 +31,8 @@ def get_input_cost(
31
31
  refusal = msg.get("refusal")
32
32
  if isinstance(content, list):
33
33
  for part in content:
34
- if 'text' in part:
35
- input_tokens += len(enc.encode(part['text']))
34
+ if "text" in part:
35
+ input_tokens += len(enc.encode(part["text"]))
36
36
  elif content is None:
37
37
  if refusal is None:
38
38
  raise ValueError("Expected content or refusal to be present")
@@ -47,7 +47,7 @@ def get_input_cost(
47
47
 
48
48
  def get_output_cost(
49
49
  cost_map: CostData,
50
- enc: tiktoken.Encoding,
50
+ enc: Tokenizer,
51
51
  item: ChatCompletionMessage | ChatCompletionMessageParam,
52
52
  ) -> tuple[float, int]:
53
53
  if isinstance(item, ChatCompletionMessage):
wcgw/client/tools.py CHANGED
@@ -19,14 +19,14 @@ from typing import (
19
19
  TypeVar,
20
20
  )
21
21
  import uuid
22
- import humanize
22
+
23
23
  from pydantic import BaseModel, TypeAdapter
24
24
  import typer
25
25
  from .computer_use import run_computer_tool
26
26
  from websockets.sync.client import connect as syncconnect
27
27
 
28
28
  import os
29
- import tiktoken
29
+ import tokenizers # type: ignore
30
30
  import pexpect
31
31
  from typer import Typer
32
32
  import websockets
@@ -47,7 +47,7 @@ from ..types_ import (
47
47
  FileEditFindReplace,
48
48
  FileEdit,
49
49
  Initialize,
50
- ReadFile,
50
+ ReadFiles,
51
51
  ReadImage,
52
52
  ResetShell,
53
53
  Mouse,
@@ -259,9 +259,17 @@ class BashState:
259
259
  def get_pending_for(self) -> str:
260
260
  if isinstance(self._state, datetime.datetime):
261
261
  timedelta = datetime.datetime.now() - self._state
262
- return humanize.naturaldelta(
263
- timedelta + datetime.timedelta(seconds=TIMEOUT)
262
+ return (
263
+ str(
264
+ int(
265
+ (
266
+ timedelta + datetime.timedelta(seconds=TIMEOUT)
267
+ ).total_seconds()
268
+ )
269
+ )
270
+ + " seconds"
264
271
  )
272
+
265
273
  return "Not pending"
266
274
 
267
275
  @property
@@ -279,16 +287,24 @@ class BashState:
279
287
  BASH_STATE = BashState()
280
288
 
281
289
 
282
- def initial_info() -> str:
290
+ def initialize(workspace_dir: str = "") -> str:
291
+ reset_shell()
292
+ if workspace_dir:
293
+ BASH_STATE.shell.sendline(f"cd {shlex.quote(workspace_dir)}")
294
+ BASH_STATE.shell.expect(PROMPT, timeout=0.2)
295
+ BASH_STATE.update_cwd()
296
+
283
297
  uname_sysname = os.uname().sysname
284
298
  uname_machine = os.uname().machine
285
- return f"""
299
+
300
+ output = f"""
286
301
  System: {uname_sysname}
287
302
  Machine: {uname_machine}
288
303
  Current working directory: {BASH_STATE.cwd}
289
- wcgw version: {importlib.metadata.version("wcgw")}
290
304
  """
291
305
 
306
+ return output
307
+
292
308
 
293
309
  def reset_shell() -> str:
294
310
  BASH_STATE.reset()
@@ -345,29 +361,11 @@ def get_status() -> str:
345
361
  T = TypeVar("T")
346
362
 
347
363
 
348
- def save_out_of_context(
349
- tokens: list[T],
350
- max_tokens: int,
351
- suffix: str,
352
- tokens_converted: Callable[[list[T]], str],
353
- ) -> tuple[str, list[Path]]:
354
- file_contents = list[str]()
355
- for i in range(0, len(tokens), max_tokens):
356
- file_contents.append(tokens_converted(tokens[i : i + max_tokens]))
357
-
358
- if len(file_contents) == 1:
359
- return file_contents[0], []
360
-
361
- rest_paths = list[Path]()
362
- for i, content in enumerate(file_contents):
363
- if i == 0:
364
- continue
365
- file_path = NamedTemporaryFile(delete=False, suffix=suffix).name
366
- with open(file_path, "w") as f:
367
- f.write(content)
368
- rest_paths.append(Path(file_path))
369
-
370
- return file_contents[0], rest_paths
364
+ def save_out_of_context(content: str, suffix: str) -> str:
365
+ file_path = NamedTemporaryFile(delete=False, suffix=suffix).name
366
+ with open(file_path, "w") as f:
367
+ f.write(content)
368
+ return file_path
371
369
 
372
370
 
373
371
  def rstrip(lines: list[str]) -> str:
@@ -397,8 +395,14 @@ def _incremental_text(text: str, last_pending_output: str) -> str:
397
395
  return rstrip(rendered)
398
396
 
399
397
 
398
+ def is_status_check(arg: BashInteraction | BashCommand) -> bool:
399
+ return isinstance(arg, BashInteraction) and (
400
+ arg.send_specials == ["Enter"] or arg.send_ascii == [10]
401
+ )
402
+
403
+
400
404
  def execute_bash(
401
- enc: tiktoken.Encoding,
405
+ enc: tokenizers.Tokenizer,
402
406
  bash_arg: BashCommand | BashInteraction,
403
407
  max_tokens: Optional[int],
404
408
  timeout_s: Optional[float],
@@ -428,7 +432,9 @@ def execute_bash(
428
432
  "Command should not contain newline character in middle. Run only one command at a time."
429
433
  )
430
434
 
431
- BASH_STATE.shell.sendline(command)
435
+ for i in range(0, len(command), 128):
436
+ BASH_STATE.shell.send(command[i : i + 128])
437
+ BASH_STATE.shell.send(BASH_STATE.shell.linesep)
432
438
 
433
439
  else:
434
440
  if (
@@ -491,7 +497,9 @@ def execute_bash(
491
497
  0,
492
498
  )
493
499
  console.print(f"Interact text: {bash_arg.send_text}")
494
- BASH_STATE.shell.sendline(bash_arg.send_text)
500
+ for i in range(0, len(bash_arg.send_text), 128):
501
+ BASH_STATE.shell.send(bash_arg.send_text[i : i + 128])
502
+ BASH_STATE.shell.send(BASH_STATE.shell.linesep)
495
503
 
496
504
  except KeyboardInterrupt:
497
505
  BASH_STATE.shell.sendintr()
@@ -505,13 +513,14 @@ def execute_bash(
505
513
  incremental_text = _incremental_text(text, BASH_STATE.pending_output)
506
514
 
507
515
  second_wait_success = False
508
- if incremental_text and isinstance(bash_arg, BashInteraction):
516
+ if is_status_check(bash_arg):
509
517
  # There's some text in BashInteraction mode wait for TIMEOUT_WHILE_OUTPUT
510
518
  remaining = TIMEOUT_WHILE_OUTPUT - wait
511
519
  patience = OUTPUT_WAIT_PATIENCE
520
+ if not incremental_text:
521
+ patience -= 1
512
522
  itext = incremental_text
513
523
  while remaining > 0 and patience > 0:
514
- print(remaining, TIMEOUT_WHILE_OUTPUT)
515
524
  index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=wait)
516
525
  if index == 0:
517
526
  second_wait_success = True
@@ -538,7 +547,7 @@ def execute_bash(
538
547
 
539
548
  if max_tokens and len(tokens) >= max_tokens:
540
549
  incremental_text = "(...truncated)\n" + enc.decode(
541
- tokens[-(max_tokens - 1) :]
550
+ tokens.ids[-(max_tokens - 1) :]
542
551
  )
543
552
 
544
553
  if is_interrupt:
@@ -562,12 +571,9 @@ def execute_bash(
562
571
  output = _incremental_text(BASH_STATE.shell.before, BASH_STATE.pending_output)
563
572
  BASH_STATE.set_repl()
564
573
 
565
- if is_interrupt:
566
- return "Interrupt successful", 0.0
567
-
568
574
  tokens = enc.encode(output)
569
575
  if max_tokens and len(tokens) >= max_tokens:
570
- output = "(...truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
576
+ output = "(...truncated)\n" + enc.decode(tokens.ids[-(max_tokens - 1) :])
571
577
 
572
578
  try:
573
579
  exit_status = get_status()
@@ -627,6 +633,19 @@ def ensure_no_previous_output(func: Callable[Param, T]) -> Callable[Param, T]:
627
633
  return wrapper
628
634
 
629
635
 
636
+ def truncate_if_over(content: str, max_tokens: Optional[int]) -> str:
637
+ if max_tokens and max_tokens > 0:
638
+ tokens = default_enc.encode(content)
639
+ n_tokens = len(tokens)
640
+ if n_tokens > max_tokens:
641
+ content = (
642
+ default_enc.decode(tokens.ids[: max(0, max_tokens - 100)])
643
+ + "\n(...truncated)"
644
+ )
645
+
646
+ return content
647
+
648
+
630
649
  def read_image_from_shell(file_path: str) -> ImageData:
631
650
  if not os.path.isabs(file_path):
632
651
  file_path = os.path.join(BASH_STATE.cwd, file_path)
@@ -655,7 +674,25 @@ def read_image_from_shell(file_path: str) -> ImageData:
655
674
  return ImageData(media_type=image_type, data=image_b64) # type: ignore
656
675
 
657
676
 
658
- def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str:
677
+ def get_context_for_errors(
678
+ errors: list[tuple[int, int]], file_content: str, max_tokens: Optional[int]
679
+ ) -> str:
680
+ file_lines = file_content.split("\n")
681
+ min_line_num = max(0, min([error[0] for error in errors]) - 10)
682
+ max_line_num = min(len(file_lines), max([error[0] for error in errors]) + 10)
683
+ context_lines = file_lines[min_line_num:max_line_num]
684
+ context = "\n".join(context_lines)
685
+
686
+ if max_tokens is not None and max_tokens > 0:
687
+ ntokens = len(default_enc.encode(context))
688
+ if ntokens > max_tokens:
689
+ return "Please re-read the file to understand the context"
690
+ return f"Here's relevant snippet from the file where the syntax errors occured:\n```\n{context}\n```"
691
+
692
+
693
+ def write_file(
694
+ writefile: WriteIfEmpty, error_on_exist: bool, max_tokens: Optional[int]
695
+ ) -> str:
659
696
  if not os.path.isabs(writefile.file_path):
660
697
  return f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}"
661
698
  else:
@@ -667,9 +704,14 @@ def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str:
667
704
  if (error_on_exist or error_on_exist_) and os.path.exists(path_):
668
705
  content = Path(path_).read_text().strip()
669
706
  if content:
707
+ content = truncate_if_over(content, max_tokens)
708
+
670
709
  if error_on_exist_:
671
- return f"Error: can't write to existing file {path_}, use other functions to edit the file"
672
- elif error_on_exist:
710
+ return (
711
+ f"Error: can't write to existing file {path_}, use other functions to edit the file"
712
+ + f"\nHere's the existing content:\n```\n{content}\n```"
713
+ )
714
+ else:
673
715
  add_overwrite_warning = content
674
716
 
675
717
  # Since we've already errored once, add this to whitelist
@@ -690,8 +732,13 @@ def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str:
690
732
  timeout=TIMEOUT,
691
733
  )
692
734
  if return_code != 0 and content.strip():
735
+ content = truncate_if_over(content, max_tokens)
736
+
693
737
  if error_on_exist_:
694
- return f"Error: can't write to existing file {path_}, use other functions to edit the file"
738
+ return (
739
+ f"Error: can't write to existing file {path_}, use other functions to edit the file"
740
+ + f"\nHere's the existing content:\n```\n{content}\n```"
741
+ )
695
742
  else:
696
743
  add_overwrite_warning = content
697
744
 
@@ -724,13 +771,19 @@ def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str:
724
771
  try:
725
772
  check = check_syntax(extension, writefile.file_content)
726
773
  syntax_errors = check.description
774
+
727
775
  if syntax_errors:
776
+ context_for_errors = get_context_for_errors(
777
+ check.errors, writefile.file_content, max_tokens
778
+ )
728
779
  console.print(f"W: Syntax errors encountered: {syntax_errors}")
729
780
  warnings.append(f"""
730
781
  ---
731
- Warning: tree-sitter reported syntax errors, please re-read the file and fix if any errors.
732
- Errors:
782
+ Warning: tree-sitter reported syntax errors
783
+ Syntax errors:
733
784
  {syntax_errors}
785
+
786
+ {context_for_errors}
734
787
  ---
735
788
  """)
736
789
 
@@ -740,8 +793,10 @@ Errors:
740
793
  if add_overwrite_warning:
741
794
  warnings.append(
742
795
  "\n---\nWarning: a file already existed and it's now overwritten. Was it a mistake? If yes please revert your action."
743
- "Here's the previous content:\n```\n" + add_overwrite_warning + "\n```"
744
796
  "\n---\n"
797
+ + "Here's the previous content:\n```\n"
798
+ + add_overwrite_warning
799
+ + "\n```"
745
800
  )
746
801
 
747
802
  return "Success" + "".join(warnings)
@@ -867,9 +922,9 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
867
922
  )
868
923
 
869
924
 
870
- def do_diff_edit(fedit: FileEdit) -> str:
925
+ def do_diff_edit(fedit: FileEdit, max_tokens: Optional[int]) -> str:
871
926
  try:
872
- return _do_diff_edit(fedit)
927
+ return _do_diff_edit(fedit, max_tokens)
873
928
  except Exception as e:
874
929
  # Try replacing \"
875
930
  try:
@@ -879,13 +934,13 @@ def do_diff_edit(fedit: FileEdit) -> str:
879
934
  '\\"', '"'
880
935
  ),
881
936
  )
882
- return _do_diff_edit(fedit)
937
+ return _do_diff_edit(fedit, max_tokens)
883
938
  except Exception:
884
939
  pass
885
940
  raise e
886
941
 
887
942
 
888
- def _do_diff_edit(fedit: FileEdit) -> str:
943
+ def _do_diff_edit(fedit: FileEdit, max_tokens: Optional[int]) -> str:
889
944
  console.log(f"Editing file: {fedit.file_path}")
890
945
 
891
946
  if not os.path.isabs(fedit.file_path):
@@ -984,13 +1039,19 @@ def _do_diff_edit(fedit: FileEdit) -> str:
984
1039
  check = check_syntax(extension, apply_diff_to)
985
1040
  syntax_errors = check.description
986
1041
  if syntax_errors:
1042
+ context_for_errors = get_context_for_errors(
1043
+ check.errors, apply_diff_to, max_tokens
1044
+ )
1045
+
987
1046
  console.print(f"W: Syntax errors encountered: {syntax_errors}")
988
1047
  return f"""Wrote file succesfully.
989
1048
  ---
990
1049
  However, tree-sitter reported syntax errors, please re-read the file and fix if there are any errors.
991
- Errors:
1050
+ Syntax errors:
992
1051
  {syntax_errors}
993
- """
1052
+
1053
+ {context_for_errors}
1054
+ """
994
1055
  except Exception:
995
1056
  pass
996
1057
 
@@ -1030,7 +1091,7 @@ TOOLS = (
1030
1091
  | AIAssistant
1031
1092
  | DoneFlag
1032
1093
  | ReadImage
1033
- | ReadFile
1094
+ | ReadFiles
1034
1095
  | Initialize
1035
1096
  | Mouse
1036
1097
  | Keyboard
@@ -1065,8 +1126,8 @@ def which_tool_name(name: str) -> Type[TOOLS]:
1065
1126
  return DoneFlag
1066
1127
  elif name == "ReadImage":
1067
1128
  return ReadImage
1068
- elif name == "ReadFile":
1069
- return ReadFile
1129
+ elif name == "ReadFiles":
1130
+ return ReadFiles
1070
1131
  elif name == "Initialize":
1071
1132
  return Initialize
1072
1133
  elif name == "Mouse":
@@ -1086,7 +1147,7 @@ TOOL_CALLS: list[TOOLS] = []
1086
1147
 
1087
1148
  def get_tool_output(
1088
1149
  args: dict[object, object] | TOOLS,
1089
- enc: tiktoken.Encoding,
1150
+ enc: tokenizers.Tokenizer,
1090
1151
  limit: float,
1091
1152
  loop_call: Callable[[str, float], tuple[str, float]],
1092
1153
  max_tokens: Optional[int],
@@ -1107,10 +1168,10 @@ def get_tool_output(
1107
1168
  output = execute_bash(enc, arg, max_tokens, arg.wait_for_seconds)
1108
1169
  elif isinstance(arg, WriteIfEmpty):
1109
1170
  console.print("Calling write file tool")
1110
- output = write_file(arg, True), 0
1171
+ output = write_file(arg, True, max_tokens), 0
1111
1172
  elif isinstance(arg, FileEdit):
1112
1173
  console.print("Calling full file edit tool")
1113
- output = do_diff_edit(arg), 0.0
1174
+ output = do_diff_edit(arg, max_tokens), 0.0
1114
1175
  elif isinstance(arg, DoneFlag):
1115
1176
  console.print("Calling mark finish tool")
1116
1177
  output = mark_finish(arg), 0.0
@@ -1120,17 +1181,15 @@ def get_tool_output(
1120
1181
  elif isinstance(arg, ReadImage):
1121
1182
  console.print("Calling read image tool")
1122
1183
  output = read_image_from_shell(arg.file_path), 0.0
1123
- elif isinstance(arg, ReadFile):
1184
+ elif isinstance(arg, ReadFiles):
1124
1185
  console.print("Calling read file tool")
1125
- output = read_file(arg, max_tokens), 0.0
1186
+ output = read_files(arg.file_paths, max_tokens), 0.0
1126
1187
  elif isinstance(arg, ResetShell):
1127
1188
  console.print("Calling reset shell tool")
1128
1189
  output = reset_shell(), 0.0
1129
1190
  elif isinstance(arg, Initialize):
1130
1191
  console.print("Calling initial info tool")
1131
- # First force reset
1132
- reset_shell()
1133
- output = initial_info(), 0.0
1192
+ output = initialize(), 0.0
1134
1193
  elif isinstance(arg, (Mouse, Keyboard, ScreenShot, GetScreenInfo)):
1135
1194
  console.print(f"Calling {type(arg).__name__} tool")
1136
1195
  outputs_cost = run_computer_tool(arg), 0.0
@@ -1179,7 +1238,9 @@ def get_tool_output(
1179
1238
 
1180
1239
  History = list[ChatCompletionMessageParam]
1181
1240
 
1182
- default_enc = tiktoken.encoding_for_model("gpt-4o")
1241
+ default_enc: tokenizers.Tokenizer = tokenizers.Tokenizer.from_pretrained(
1242
+ "Xenova/claude-tokenizer"
1243
+ )
1183
1244
  curr_cost = 0.0
1184
1245
 
1185
1246
 
@@ -1192,7 +1253,7 @@ class Mdata(BaseModel):
1192
1253
  | FileEditFindReplace
1193
1254
  | FileEdit
1194
1255
  | str
1195
- | ReadFile
1256
+ | ReadFiles
1196
1257
  | Initialize
1197
1258
  )
1198
1259
 
@@ -1265,43 +1326,69 @@ def app(
1265
1326
  register_client(server_url, client_uuid or "")
1266
1327
 
1267
1328
 
1268
- def read_file(readfile: ReadFile, max_tokens: Optional[int]) -> str:
1269
- console.print(f"Reading file: {readfile.file_path}")
1329
+ def read_files(file_paths: list[str], max_tokens: Optional[int]) -> str:
1330
+ message = ""
1331
+ for i, file in enumerate(file_paths):
1332
+ try:
1333
+ content, truncated, tokens = read_file(file, max_tokens)
1334
+ except ValueError as e:
1335
+ message += f"\n{file}: {str(e)}\n"
1336
+ continue
1337
+
1338
+ if max_tokens:
1339
+ max_tokens = max_tokens - tokens
1340
+
1341
+ message += f"\n``` {file}\n{content}\n"
1342
+
1343
+ if truncated or (max_tokens and max_tokens <= 0):
1344
+ not_reading = file_paths[i + 1 :]
1345
+ if not_reading:
1346
+ message += f'\nNot reading the rest of the files: {", ".join(not_reading)} due to token limit, please call again'
1347
+ break
1348
+ else:
1349
+ message += "```"
1350
+
1351
+ return message
1270
1352
 
1271
- if not os.path.isabs(readfile.file_path):
1272
- return f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}"
1273
1353
 
1274
- BASH_STATE.add_to_whitelist_for_overwrite(readfile.file_path)
1354
+ def read_file(file_path: str, max_tokens: Optional[int]) -> tuple[str, bool, int]:
1355
+ console.print(f"Reading file: {file_path}")
1356
+
1357
+ if not os.path.isabs(file_path):
1358
+ raise ValueError(
1359
+ f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}"
1360
+ )
1361
+
1362
+ BASH_STATE.add_to_whitelist_for_overwrite(file_path)
1275
1363
 
1276
1364
  if not BASH_STATE.is_in_docker:
1277
- path = Path(readfile.file_path)
1365
+ path = Path(file_path)
1278
1366
  if not path.exists():
1279
- return f"Error: file {readfile.file_path} does not exist"
1367
+ raise ValueError(f"Error: file {file_path} does not exist")
1280
1368
 
1281
1369
  with path.open("r") as f:
1282
- content = f.read()
1370
+ content = f.read(10_000_000)
1283
1371
 
1284
1372
  else:
1285
1373
  return_code, content, stderr = command_run(
1286
- f"docker exec {BASH_STATE.is_in_docker} cat {shlex.quote(readfile.file_path)}",
1374
+ f"docker exec {BASH_STATE.is_in_docker} cat {shlex.quote(file_path)}",
1287
1375
  timeout=TIMEOUT,
1288
1376
  )
1289
1377
  if return_code != 0:
1290
1378
  raise Exception(
1291
- f"Error: cat {readfile.file_path} failed with code {return_code}\nstdout: {content}\nstderr: {stderr}"
1379
+ f"Error: cat {file_path} failed with code {return_code}\nstdout: {content}\nstderr: {stderr}"
1292
1380
  )
1293
1381
 
1382
+ truncated = False
1383
+ tokens_counts = 0
1294
1384
  if max_tokens is not None:
1295
1385
  tokens = default_enc.encode(content)
1386
+ tokens_counts = len(tokens)
1296
1387
  if len(tokens) > max_tokens:
1297
- content, rest = save_out_of_context(
1298
- tokens,
1299
- max_tokens - 100,
1300
- Path(readfile.file_path).suffix,
1301
- default_enc.decode,
1388
+ content = default_enc.decode(tokens.ids[:max_tokens])
1389
+ rest = save_out_of_context(
1390
+ default_enc.decode(tokens.ids[max_tokens:]), Path(file_path).suffix
1302
1391
  )
1303
- if rest:
1304
- rest_ = "\n".join(map(str, rest))
1305
- content += f"\n(...truncated)\n---\nI've split the rest of the file into multiple files. Here are the remaining splits, please read them:\n{rest_}"
1306
-
1307
- return content
1392
+ content += f"\n(...truncated)\n---\nI've saved the continuation in a new file. Please read: `{rest}`"
1393
+ truncated = True
1394
+ return content, truncated, tokens_counts
wcgw/relay/serve.py CHANGED
@@ -21,7 +21,7 @@ from ..types_ import (
21
21
  FileEditFindReplace,
22
22
  FileEdit,
23
23
  Initialize,
24
- ReadFile,
24
+ ReadFiles,
25
25
  ResetShell,
26
26
  Specials,
27
27
  )
@@ -35,7 +35,7 @@ class Mdata(BaseModel):
35
35
  | ResetShell
36
36
  | FileEditFindReplace
37
37
  | FileEdit
38
- | ReadFile
38
+ | ReadFiles
39
39
  | Initialize
40
40
  | str
41
41
  )
@@ -259,7 +259,7 @@ async def bash_interaction(bash_interaction: BashInteractionWithUUID) -> str:
259
259
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
260
260
 
261
261
 
262
- class ReadFileWithUUID(ReadFile):
262
+ class ReadFileWithUUID(ReadFiles):
263
263
  user_id: UUID
264
264
 
265
265
 
wcgw/types_.py CHANGED
@@ -31,9 +31,9 @@ class WriteIfEmpty(BaseModel):
31
31
  file_content: str
32
32
 
33
33
 
34
- class ReadFile(BaseModel):
35
- file_path: str # The path to the file to read
36
- type: Literal["ReadFile"]
34
+ class ReadFiles(BaseModel):
35
+ file_paths: list[str]
36
+ type: Literal["ReadFiles"]
37
37
 
38
38
 
39
39
  class FileEditFindReplace(BaseModel):
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 2.4.2
3
+ Version: 2.5.0
4
4
  Summary: Shell and coding agent on claude and chatgpt
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
7
+ License-File: LICENSE
7
8
  Requires-Python: <3.13,>=3.11
8
9
  Requires-Dist: anthropic>=0.39.0
9
10
  Requires-Dist: fastapi>=0.115.0
10
- Requires-Dist: humanize>=4.11.0
11
11
  Requires-Dist: openai>=1.46.0
12
12
  Requires-Dist: petname>=2.6
13
13
  Requires-Dist: pexpect>=4.9.0
@@ -18,7 +18,7 @@ Requires-Dist: rich>=13.8.1
18
18
  Requires-Dist: semantic-version>=2.10.0
19
19
  Requires-Dist: shell>=1.0.1
20
20
  Requires-Dist: syntax-checker==0.2.10
21
- Requires-Dist: tiktoken==0.7.0
21
+ Requires-Dist: tokenizers>=0.21.0
22
22
  Requires-Dist: toml>=0.10.2
23
23
  Requires-Dist: typer>=0.12.5
24
24
  Requires-Dist: types-pexpect>=4.9.0.20240806
@@ -1,20 +1,20 @@
1
1
  wcgw/__init__.py,sha256=9K2QW7QuSLhMTVbKbBYd9UUp-ZyrfBrxcjuD_xk458k,118
2
- wcgw/types_.py,sha256=9h0-UKS4emx12eI24VSfgvz8WW0p5hwxFwzq8Wvbk6w,1858
2
+ wcgw/types_.py,sha256=NDWBfzmR89-L7rFuf-9PeCF3jZuQ-WWMLbRVP-qlnJw,1835
3
3
  wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  wcgw/client/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
5
- wcgw/client/anthropic_client.py,sha256=ymMi2Kmkcso_6PT8iuwsqLz4Ne6oTvId3OWBB4La3gc,21147
5
+ wcgw/client/anthropic_client.py,sha256=kmS93LEVwXCppfbgCsfWe_SVmTAtsI3x8PrP8DJDYMY,21041
6
6
  wcgw/client/cli.py,sha256=-z0kpDAW3mzfQrQeZfaVJhBCAQY3HXnt9GdgQ8s-u0Y,1003
7
- wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
7
+ wcgw/client/common.py,sha256=OCH7Tx64jojz3M3iONUrGMadE07W21DiZs5sOxWX1Qc,1456
8
8
  wcgw/client/computer_use.py,sha256=35NKAlMrxwD0TBlMMRnbCwz4g8TBRGOlcy-cmS-yJ_A,15247
9
9
  wcgw/client/diff-instructions.txt,sha256=s5AJKG23JsjwRYhFZFQVvwDpF67vElawrmdXwvukR1A,1683
10
- wcgw/client/openai_client.py,sha256=uJ2l9NXsZuipUcJYR_bFcNNmNlfnCvPm6-M-LiVSVts,17942
11
- wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
10
+ wcgw/client/openai_client.py,sha256=hAoUF4wcD0LqqX9MjqWZux12_HH0EdVognNcRLG6pew,17903
11
+ wcgw/client/openai_utils.py,sha256=KfMB1-p2zDiA7pPWwAVarochf7-qeL1UMgtlDV9DtKA,2662
12
12
  wcgw/client/sys_utils.py,sha256=GajPntKhaTUMn6EOmopENWZNR2G_BJyuVbuot0x6veI,1376
13
- wcgw/client/tools.py,sha256=ZLUlVDS-G-_UId5nJoeXINlvjvP2DUGLuTAFmHTDUvw,44131
13
+ wcgw/client/tools.py,sha256=HO4xO0D7oxYJGeof0O-GIADc9UkoPwRbmyh9SgAWTMw,47079
14
14
  wcgw/client/mcp_server/Readme.md,sha256=I8N4dHkTUVGNQ63BQkBMBhCCBTgqGOSF_pUR6iOEiUk,2495
15
15
  wcgw/client/mcp_server/__init__.py,sha256=hyPPwO9cabAJsOMWhKyat9yl7OlSmIobaoAZKHu3DMc,381
16
- wcgw/client/mcp_server/server.py,sha256=CNUOAd83lCq0Ed_ZRwd66gIjMFN9VBSO4moTLUPTWwM,11956
17
- wcgw/relay/serve.py,sha256=KLYjTvM9CfqdxgFOfHM8LUkFGZ9kKyyJunpNdEIFQUk,8766
16
+ wcgw/client/mcp_server/server.py,sha256=d8fegYsneGnmzwqCCL1vkHD_DuB2uQGyg24P_KvAK-A,12024
17
+ wcgw/relay/serve.py,sha256=CYY0mAAzR6nXkdGqLA9dXkgBcMCKPXEAmBcDyutUnjQ,8769
18
18
  wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
19
19
  mcp_wcgw/__init__.py,sha256=fKCgOdN7cn7gR3YGFaGyV5Goe8A2sEyllLcsRkN0i-g,2601
20
20
  mcp_wcgw/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -38,7 +38,8 @@ mcp_wcgw/shared/memory.py,sha256=dBsOghxHz8-tycdSVo9kSujbsC8xb_tYsGmuJobuZnw,281
38
38
  mcp_wcgw/shared/progress.py,sha256=ymxOsb8XO5Mhlop7fRfdbmvPodANj7oq6O4dD0iUcnw,1048
39
39
  mcp_wcgw/shared/session.py,sha256=e44a0LQOW8gwdLs9_DE9oDsxqW2U8mXG3d5KT95bn5o,10393
40
40
  mcp_wcgw/shared/version.py,sha256=d2LZii-mgsPIxpshjkXnOTUmk98i0DT4ff8VpA_kAvE,111
41
- wcgw-2.4.2.dist-info/METADATA,sha256=hyO0tbDU7FBSsRNcuX8tnbyg_j2pTtdWm7JG9GY-UZQ,7931
42
- wcgw-2.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
- wcgw-2.4.2.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
44
- wcgw-2.4.2.dist-info/RECORD,,
41
+ wcgw-2.5.0.dist-info/METADATA,sha256=D16uxd1XiajjITvdEIdE2b4MI9gYepkor39zOkOQvFI,7924
42
+ wcgw-2.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
+ wcgw-2.5.0.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
44
+ wcgw-2.5.0.dist-info/licenses/LICENSE,sha256=SDdQZzFz8ehsr0m87ZmC3LL82leZdULi2yJ-XGGuqac,13417
45
+ wcgw-2.5.0.dist-info/RECORD,,
@@ -0,0 +1,243 @@
1
+ The majority of this software is licensed under the MIT License.
2
+ Portions of this software include code licensed under the Apache License, Version 2.0.
3
+
4
+ --------------------------------------------------------------------------------
5
+ MIT License (Primary License)
6
+ --------------------------------------------------------------------------------
7
+
8
+ Copyright (c) 2024 arusia
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ --------------------------------------------------------------------------------
29
+ Third Party Components
30
+ --------------------------------------------------------------------------------
31
+
32
+ This project includes components from the following third-party projects:
33
+
34
+ 1. The file at src/wcgw/client/diff-instructions.txt is substantially modified from Aider
35
+ Source: https://github.com/Aider-AI/aider
36
+ Original Copyright (c) 2023 Aider-AI
37
+ Licensed under the Apache License, Version 2.0
38
+ Modified version Copyright (c) 2024 arusia
39
+
40
+ The full text of the Apache License 2.0 follows:
41
+
42
+ --------------------------------------------------------------------------------
43
+ Apache License
44
+ Version 2.0, January 2004
45
+ http://www.apache.org/licenses/
46
+
47
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
48
+
49
+ 1. Definitions.
50
+
51
+ "License" shall mean the terms and conditions for use, reproduction,
52
+ and distribution as defined by Sections 1 through 9 of this document.
53
+
54
+ "Licensor" shall mean the copyright owner or entity authorized by
55
+ the copyright owner that is granting the License.
56
+
57
+ "Legal Entity" shall mean the union of the acting entity and all
58
+ other entities that control, are controlled by, or are under common
59
+ control with that entity. For the purposes of this definition,
60
+ "control" means (i) the power, direct or indirect, to cause the
61
+ direction or management of such entity, whether by contract or
62
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
63
+ outstanding shares, or (iii) beneficial ownership of such entity.
64
+
65
+ "You" (or "Your") shall mean an individual or Legal Entity
66
+ exercising permissions granted by this License.
67
+
68
+ "Source" form shall mean the preferred form for making modifications,
69
+ including but not limited to software source code, documentation
70
+ source, and configuration files.
71
+
72
+ "Object" form shall mean any form resulting from mechanical
73
+ transformation or translation of a Source form, including but
74
+ not limited to compiled object code, generated documentation,
75
+ and conversions to other media types.
76
+
77
+ "Work" shall mean the work of authorship, whether in Source or
78
+ Object form, made available under the License, as indicated by a
79
+ copyright notice that is included in or attached to the work
80
+ (an example is provided in the Appendix below).
81
+
82
+ "Derivative Works" shall mean any work, whether in Source or Object
83
+ form, that is based on (or derived from) the Work and for which the
84
+ editorial revisions, annotations, elaborations, or other modifications
85
+ represent, as a whole, an original work of authorship. For the purposes
86
+ of this License, Derivative Works shall not include works that remain
87
+ separable from, or merely link (or bind by name) to the interfaces of,
88
+ the Work and Derivative Works thereof.
89
+
90
+ "Contribution" shall mean any work of authorship, including
91
+ the original version of the Work and any modifications or additions
92
+ to that Work or Derivative Works thereof, that is intentionally
93
+ submitted to Licensor for inclusion in the Work by the copyright owner
94
+ or by an individual or Legal Entity authorized to submit on behalf of
95
+ the copyright owner. For the purposes of this definition, "submitted"
96
+ means any form of electronic, verbal, or written communication sent
97
+ to the Licensor or its representatives, including but not limited to
98
+ communication on electronic mailing lists, source code control systems,
99
+ and issue tracking systems that are managed by, or on behalf of, the
100
+ Licensor for the purpose of discussing and improving the Work, but
101
+ excluding communication that is conspicuously marked or otherwise
102
+ designated in writing by the copyright owner as "Not a Contribution."
103
+
104
+ "Contributor" shall mean Licensor and any individual or Legal Entity
105
+ on behalf of whom a Contribution has been received by Licensor and
106
+ subsequently incorporated within the Work.
107
+
108
+ 2. Grant of Copyright License. Subject to the terms and conditions of
109
+ this License, each Contributor hereby grants to You a perpetual,
110
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
111
+ copyright license to reproduce, prepare Derivative Works of,
112
+ publicly display, publicly perform, sublicense, and distribute the
113
+ Work and such Derivative Works in Source or Object form.
114
+
115
+ 3. Grant of Patent License. Subject to the terms and conditions of
116
+ this License, each Contributor hereby grants to You a perpetual,
117
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
118
+ (except as stated in this section) patent license to make, have made,
119
+ use, offer to sell, sell, import, and otherwise transfer the Work,
120
+ where such license applies only to those patent claims licensable
121
+ by such Contributor that are necessarily infringed by their
122
+ Contribution(s) alone or by combination of their Contribution(s)
123
+ with the Work to which such Contribution(s) was submitted. If You
124
+ institute patent litigation against any entity (including a
125
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
126
+ or a Contribution incorporated within the Work constitutes direct
127
+ or contributory patent infringement, then any patent licenses
128
+ granted to You under this License for that Work shall terminate
129
+ as of the date such litigation is filed.
130
+
131
+ 4. Redistribution. You may reproduce and distribute copies of the
132
+ Work or Derivative Works thereof in any medium, with or without
133
+ modifications, and in Source or Object form, provided that You
134
+ meet the following conditions:
135
+
136
+ (a) You must give any other recipients of the Work or
137
+ Derivative Works a copy of this License; and
138
+
139
+ (b) You must cause any modified files to carry prominent notices
140
+ stating that You changed the files; and
141
+
142
+ (c) You must retain, in the Source form of any Derivative Works
143
+ that You distribute, all copyright, patent, trademark, and
144
+ attribution notices from the Source form of the Work,
145
+ excluding those notices that do not pertain to any part of
146
+ the Derivative Works; and
147
+
148
+ (d) If the Work includes a "NOTICE" text file as part of its
149
+ distribution, then any Derivative Works that You distribute must
150
+ include a readable copy of the attribution notices contained
151
+ within such NOTICE file, excluding those notices that do not
152
+ pertain to any part of the Derivative Works, in at least one
153
+ of the following places: within a NOTICE text file distributed
154
+ as part of the Derivative Works; within the Source form or
155
+ documentation, if provided along with the Derivative Works; or,
156
+ within a display generated by the Derivative Works, if and
157
+ wherever such third-party notices normally appear. The contents
158
+ of the NOTICE file are for informational purposes only and
159
+ do not modify the License. You may add Your own attribution
160
+ notices within Derivative Works that You distribute, alongside
161
+ or as an addendum to the NOTICE text from the Work, provided
162
+ that such additional attribution notices cannot be construed
163
+ as modifying the License.
164
+
165
+ You may add Your own copyright statement to Your modifications and
166
+ may provide additional or different license terms and conditions
167
+ for use, reproduction, or distribution of Your modifications, or
168
+ for any such Derivative Works as a whole, provided Your use,
169
+ reproduction, and distribution of the Work otherwise complies with
170
+ the conditions stated in this License.
171
+
172
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
173
+ any Contribution intentionally submitted for inclusion in the Work
174
+ by You to the Licensor shall be under the terms and conditions of
175
+ this License, without any additional terms or conditions.
176
+ Notwithstanding the above, nothing herein shall supersede or modify
177
+ the terms of any separate license agreement you may have executed
178
+ with Licensor regarding such Contributions.
179
+
180
+ 6. Trademarks. This License does not grant permission to use the trade
181
+ names, trademarks, service marks, or product names of the Licensor,
182
+ except as required for reasonable and customary use in describing the
183
+ origin of the Work and reproducing the content of the NOTICE file.
184
+
185
+ 7. Disclaimer of Warranty. Unless required by applicable law or
186
+ agreed to in writing, Licensor provides the Work (and each
187
+ Contributor provides its Contributions) on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
189
+ implied, including, without limitation, any warranties or conditions
190
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
191
+ PARTICULAR PURPOSE. You are solely responsible for determining the
192
+ appropriateness of using or redistributing the Work and assume any
193
+ risks associated with Your exercise of permissions under this License.
194
+
195
+ 8. Limitation of Liability. In no event and under no legal theory,
196
+ whether in tort (including negligence), contract, or otherwise,
197
+ unless required by applicable law (such as deliberate and grossly
198
+ negligent acts) or agreed to in writing, shall any Contributor be
199
+ liable to You for damages, including any direct, indirect, special,
200
+ incidental, or consequential damages of any character arising as a
201
+ result of this License or out of the use or inability to use the
202
+ Work (including but not limited to damages for loss of goodwill,
203
+ work stoppage, computer failure or malfunction, or any and all
204
+ other commercial damages or losses), even if such Contributor
205
+ has been advised of the possibility of such damages.
206
+
207
+ 9. Accepting Warranty or Additional Liability. While redistributing
208
+ the Work or Derivative Works thereof, You may choose to offer,
209
+ and charge a fee for, acceptance of support, warranty, indemnity,
210
+ or other liability obligations and/or rights consistent with this
211
+ License. However, in accepting such obligations, You may act only
212
+ on Your own behalf and on Your sole responsibility, not on behalf
213
+ of any other Contributor, and only if You agree to indemnify,
214
+ defend, and hold each Contributor harmless for any liability
215
+ incurred by, or claims asserted against, such Contributor by reason
216
+ of your accepting any such warranty or additional liability.
217
+
218
+ END OF TERMS AND CONDITIONS
219
+
220
+ APPENDIX: How to apply the Apache License to your work.
221
+
222
+ To apply the Apache License to your work, attach the following
223
+ boilerplate notice, with the fields enclosed by brackets "[]"
224
+ replaced with your own identifying information. (Don't include
225
+ the brackets!) The text should be enclosed in the appropriate
226
+ comment syntax for the file format. We also recommend that a
227
+ file or class name and description of purpose be included on the
228
+ same "printed page" as the copyright notice for easier
229
+ identification within third-party archives.
230
+
231
+ Copyright [yyyy] [name of copyright owner]
232
+
233
+ Licensed under the Apache License, Version 2.0 (the "License");
234
+ you may not use this file except in compliance with the License.
235
+ You may obtain a copy of the License at
236
+
237
+ http://www.apache.org/licenses/LICENSE-2.0
238
+
239
+ Unless required by applicable law or agreed to in writing, software
240
+ distributed under the License is distributed on an "AS IS" BASIS,
241
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
242
+ See the License for the specific language governing permissions and
243
+ limitations under the License.
File without changes