wcgw 1.1.1__tar.gz → 1.2.0__tar.gz

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.

Files changed (29) hide show
  1. {wcgw-1.1.1 → wcgw-1.2.0}/PKG-INFO +1 -1
  2. {wcgw-1.1.1 → wcgw-1.2.0}/gpt_action_json_schema.json +75 -7
  3. {wcgw-1.1.1 → wcgw-1.2.0}/gpt_instructions.txt +29 -34
  4. {wcgw-1.1.1 → wcgw-1.2.0}/pyproject.toml +1 -1
  5. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/anthropic_client.py +16 -7
  6. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/diff-instructions.txt +6 -15
  7. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/openai_client.py +27 -12
  8. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/tools.py +87 -44
  9. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/relay/serve.py +46 -8
  10. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/types_.py +16 -3
  11. {wcgw-1.1.1 → wcgw-1.2.0}/uv.lock +1 -1
  12. wcgw-1.1.1/config.toml +0 -11
  13. {wcgw-1.1.1 → wcgw-1.2.0}/.github/workflows/python-publish.yml +0 -0
  14. {wcgw-1.1.1 → wcgw-1.2.0}/.github/workflows/python-tests.yml +0 -0
  15. {wcgw-1.1.1 → wcgw-1.2.0}/.gitignore +0 -0
  16. {wcgw-1.1.1 → wcgw-1.2.0}/.python-version +0 -0
  17. {wcgw-1.1.1 → wcgw-1.2.0}/.vscode/settings.json +0 -0
  18. {wcgw-1.1.1 → wcgw-1.2.0}/README.md +0 -0
  19. {wcgw-1.1.1 → wcgw-1.2.0}/src/__init__.py +0 -0
  20. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/__init__.py +0 -0
  21. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/__init__.py +0 -0
  22. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/__main__.py +0 -0
  23. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/cli.py +0 -0
  24. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/common.py +0 -0
  25. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/client/openai_utils.py +0 -0
  26. {wcgw-1.1.1 → wcgw-1.2.0}/src/wcgw/relay/static/privacy.txt +0 -0
  27. {wcgw-1.1.1 → wcgw-1.2.0}/static/ss1.png +0 -0
  28. {wcgw-1.1.1 → wcgw-1.2.0}/tests/test_basic.py +0 -0
  29. {wcgw-1.1.1 → wcgw-1.2.0}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 1.1.1
3
+ Version: 1.2.0
4
4
  Summary: What could go wrong giving full shell access to chatgpt?
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "servers": [
8
8
  {
9
- "url": "https://7d75-103-212-152-58.ngrok-free.app"
9
+ "url": "https://a0e8-125-99-67-222.ngrok-free.app"
10
10
  }
11
11
  ],
12
12
  "paths": {
@@ -59,7 +59,7 @@
59
59
  "content": {
60
60
  "application/json": {
61
61
  "schema": {
62
- "$ref": "#/components/schemas/FullFileEditWithUUID"
62
+ "$ref": "#/components/schemas/FileEditWithUUID"
63
63
  }
64
64
  }
65
65
  },
@@ -209,6 +209,46 @@
209
209
  }
210
210
  }
211
211
  }
212
+ },
213
+ "/v1/read_file": {
214
+ "post": {
215
+ "x-openai-isConsequential": false,
216
+ "summary": "Read File Endpoint",
217
+ "operationId": "read_file_endpoint_v1_read_file_post",
218
+ "requestBody": {
219
+ "content": {
220
+ "application/json": {
221
+ "schema": {
222
+ "$ref": "#/components/schemas/ReadFileWithUUID"
223
+ }
224
+ }
225
+ },
226
+ "required": true
227
+ },
228
+ "responses": {
229
+ "200": {
230
+ "description": "Successful Response",
231
+ "content": {
232
+ "application/json": {
233
+ "schema": {
234
+ "type": "string",
235
+ "title": "Response Read File Endpoint V1 Read File Post"
236
+ }
237
+ }
238
+ }
239
+ },
240
+ "422": {
241
+ "description": "Validation Error",
242
+ "content": {
243
+ "application/json": {
244
+ "schema": {
245
+ "$ref": "#/components/schemas/HTTPValidationError"
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
212
252
  }
213
253
  },
214
254
  "components": {
@@ -319,15 +359,15 @@
319
359
  ],
320
360
  "title": "CreateFileNewWithUUID"
321
361
  },
322
- "FullFileEditWithUUID": {
362
+ "FileEditWithUUID": {
323
363
  "properties": {
324
364
  "file_path": {
325
365
  "type": "string",
326
366
  "title": "File Path"
327
367
  },
328
- "file_edit_using_searh_replace_blocks": {
368
+ "file_edit_using_search_replace_blocks": {
329
369
  "type": "string",
330
- "title": "File Edit Using Searh Replace Blocks"
370
+ "title": "File Edit Using Search Replace Blocks"
331
371
  },
332
372
  "user_id": {
333
373
  "type": "string",
@@ -338,10 +378,10 @@
338
378
  "type": "object",
339
379
  "required": [
340
380
  "file_path",
341
- "file_edit_using_searh_replace_blocks",
381
+ "file_edit_using_search_replace_blocks",
342
382
  "user_id"
343
383
  ],
344
- "title": "FullFileEditWithUUID"
384
+ "title": "FileEditWithUUID"
345
385
  },
346
386
  "HTTPValidationError": {
347
387
  "properties": {
@@ -356,6 +396,34 @@
356
396
  "type": "object",
357
397
  "title": "HTTPValidationError"
358
398
  },
399
+ "ReadFileWithUUID": {
400
+ "properties": {
401
+ "file_path": {
402
+ "type": "string",
403
+ "title": "File Path"
404
+ },
405
+ "type": {
406
+ "type": "string",
407
+ "enum": [
408
+ "ReadFile"
409
+ ],
410
+ "const": "ReadFile",
411
+ "title": "Type"
412
+ },
413
+ "user_id": {
414
+ "type": "string",
415
+ "format": "uuid",
416
+ "title": "User Id"
417
+ }
418
+ },
419
+ "type": "object",
420
+ "required": [
421
+ "file_path",
422
+ "type",
423
+ "user_id"
424
+ ],
425
+ "title": "ReadFileWithUUID"
426
+ },
359
427
  "ResetShellWithUUID": {
360
428
  "properties": {
361
429
  "should_reset": {
@@ -1,14 +1,13 @@
1
- You're a cli assistant.
1
+ You're an expert software engineer with shell and code knowledge.
2
2
 
3
3
  Instructions:
4
4
 
5
- - You should use the provided bash execution tool to run script to complete objective.
6
- - Do not use sudo. Do not use interactive commands.
7
- - Ask user for confirmation before running anything major
5
+ - You should use the provided bash execution, reading and writing file tools to complete objective.
6
+ - First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
7
+ - Always read relevant files before editing.
8
+ - Do not provide code snippets unless asked by the user, instead directly edit the code.
8
9
 
9
10
 
10
- To execute bash commands OR write files use the provided api.
11
-
12
11
  Instructions for `BashCommand`:
13
12
  - Execute a bash command. This is stateful (beware with subsequent calls).
14
13
  - Do not use interactive commands like nano. Prefer writing simpler commands.
@@ -16,11 +15,15 @@ Instructions for `BashCommand`:
16
15
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
17
16
  - The first line might be `(...truncated)` if the output is too long.
18
17
 
18
+ Instructions for `Read File`
19
+ - Read full content of a file.
20
+ - Provide absolute file path only.
21
+
19
22
  Instructions for `Create File New`
20
23
  - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
21
24
  - This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
22
25
  - Provide absolute file path only.
23
- - For editing existing files, use FullFileEdit.
26
+ - For editing existing files, use FileEdit.
24
27
 
25
28
  Instructions for `BashInteraction`
26
29
  - Interact with running program using this tool
@@ -31,42 +34,33 @@ Instructions for `BashInteraction`
31
34
  Instructions for `ResetShell`
32
35
  - Resets the shell. Use only if all interrupts and prompt reset attempts have failed repeatedly.
33
36
 
34
- Instructions for `FullFileEdit`:
37
+ Instructions for `FileEdit`:
35
38
  - Use absolute file path only.
36
39
  - Use SEARCH/REPLACE blocks to edit the file.
37
40
  Only edit the files using the following SEARCH/REPLACE blocks.
38
- ```
39
- <<<<<<< SEARCH
40
- =======
41
- def hello():
42
- "print a greeting"
43
-
44
- print("hello")
45
- >>>>>>> REPLACE
46
-
47
- <<<<<<< SEARCH
48
- def hello():
49
- "print a greeting"
50
-
51
- print("hello")
52
- =======
53
- from hello import hello
54
- >>>>>>> REPLACE
55
- ```
41
+ ```
42
+ <<<<<<< SEARCH
43
+ // Original code
44
+ =======
45
+ // New code
46
+ >>>>>>> REPLACE
47
+ <<<<<<< SEARCH
48
+ // Original code further down in the file
49
+ =======
50
+ // New code to replace
51
+ >>>>>>> REPLACE
52
+ ```
56
53
 
57
54
  # *SEARCH/REPLACE block* Rules:
58
55
 
59
56
  Every *SEARCH/REPLACE block* must use this format:
60
- 1. The start of search block: <<<<<<< SEARCH
61
- 2. A contiguous chunk of lines to search for in the existing source code
57
+ 1. The start of match block: <<<<<<< SEARCH
58
+ 2. A contiguous chunk of lines to do exact match for in the existing source code
62
59
  3. The dividing line: =======
63
60
  4. The lines to replace into the source code
64
61
  5. The end of the replace block: >>>>>>> REPLACE
65
62
 
66
- Use the *FULL* file path, as shown to you by the user.
67
-
68
- Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
69
- If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
63
+ Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
70
64
 
71
65
  *SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
72
66
  Including multiple unique *SEARCH/REPLACE* blocks if needed.
@@ -77,10 +71,11 @@ Instructions for `FullFileEdit`:
77
71
  Include just the changing lines, and a few surrounding lines if needed for uniqueness.
78
72
  Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
79
73
 
74
+ Include context lines before and after the code to edit for better matching in "<<<<<<< SEARCH" and ">>>>>>> REPLACE". Recommended to add 3 lines on each side.
75
+
80
76
  ---
81
77
  Always run `pwd` if you get any file or directory not found error to make sure you're not lost, or to get absolute cwd.
82
78
 
83
- Always critically think and debate with yourself to solve the problem. Understand the context and the code by reading as much resources as possible before writing a single piece of code.
84
-
79
+ Always write production ready, syntactically correct code.
85
80
  ---
86
81
  Ask the user for the user_id `UUID` if they haven't provided in the first message.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
3
3
  name = "wcgw"
4
- version = "1.1.1"
4
+ version = "1.2.0"
5
5
  description = "What could go wrong giving full shell access to chatgpt?"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.10, <3.13"
@@ -26,7 +26,8 @@ from ..types_ import (
26
26
  BashInteraction,
27
27
  CreateFileNew,
28
28
  FileEditFindReplace,
29
- FullFileEdit,
29
+ FileEdit,
30
+ ReadFile,
30
31
  ReadImage,
31
32
  Writefile,
32
33
  ResetShell,
@@ -175,6 +176,14 @@ def loop(
175
176
  - Send text input to the running program.
176
177
  - Send send_specials=["Enter"] to recheck status of a running program.
177
178
  - Only one of send_text, send_specials, send_ascii should be provided.
179
+ """,
180
+ ),
181
+ ToolParam(
182
+ input_schema=ReadFile.model_json_schema(),
183
+ name="ReadFile",
184
+ description="""
185
+ - Read full file content
186
+ - Provide absolute file path only
178
187
  """,
179
188
  ),
180
189
  ToolParam(
@@ -184,7 +193,7 @@ def loop(
184
193
  - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
185
194
  - This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
186
195
  - Provide absolute file path only.
187
- - For editing existing files, use FullFileEdit.
196
+ - For editing existing files, use FileEdit.
188
197
  """,
189
198
  ),
190
199
  ToolParam(
@@ -198,8 +207,8 @@ def loop(
198
207
  description="Resets the shell. Use only if all interrupts and prompt reset attempts have failed repeatedly.",
199
208
  ),
200
209
  ToolParam(
201
- input_schema=FullFileEdit.model_json_schema(),
202
- name="FullFileEdit",
210
+ input_schema=FileEdit.model_json_schema(),
211
+ name="FileEdit",
203
212
  description="""
204
213
  - Use absolute file path only.
205
214
  - Use SEARCH/REPLACE blocks to edit the file.
@@ -214,9 +223,9 @@ You're a cli assistant.
214
223
 
215
224
  Instructions:
216
225
 
217
- - You should use the provided bash execution tool to run script to complete objective.
218
- - Do not use sudo. Do not use interactive commands.
219
- - Ask user for confirmation before running anything major
226
+ - You should use the provided bash execution tool to run script to complete objective.
227
+ - First understand about the project by understanding the folder structure (ignoring node_modules or venv, etc.)
228
+ - Always read relevant files before making any changes.
220
229
 
221
230
  System information:
222
231
  - System: {uname_sysname}
@@ -2,14 +2,6 @@
2
2
  Instructions for
3
3
  Only edit the files using the following SEARCH/REPLACE blocks.
4
4
  ```
5
- <<<<<<< SEARCH
6
- =======
7
- def hello():
8
- "print a greeting"
9
-
10
- print("hello")
11
- >>>>>>> REPLACE
12
-
13
5
  <<<<<<< SEARCH
14
6
  def hello():
15
7
  "print a greeting"
@@ -23,16 +15,13 @@ from hello import hello
23
15
  # *SEARCH/REPLACE block* Rules:
24
16
 
25
17
  Every *SEARCH/REPLACE block* must use this format:
26
- 1. The start of search block: <<<<<<< SEARCH
27
- 2. A contiguous chunk of lines to search for in the existing source code
18
+ 1. The start of match block: <<<<<<< SEARCH
19
+ 2. A contiguous chunk of lines to do exact match for in the existing source code
28
20
  3. The dividing line: =======
29
21
  4. The lines to replace into the source code
30
22
  5. The end of the replace block: >>>>>>> REPLACE
31
23
 
32
- Use the *FULL* file path, as shown to you by the user.
33
-
34
- Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
35
- If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
24
+ Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
36
25
 
37
26
  *SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
38
27
  Including multiple unique *SEARCH/REPLACE* blocks if needed.
@@ -41,4 +30,6 @@ Include enough lines in each SEARCH section to uniquely match each set of lines
41
30
  Keep *SEARCH/REPLACE* blocks concise.
42
31
  Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
43
32
  Include just the changing lines, and a few surrounding lines if needed for uniqueness.
44
- Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
33
+ Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
34
+
35
+ Include context lines before and after the code to edit for better matching in "<<<<<<< SEARCH". Recommended to add 3 lines on each side.
@@ -24,8 +24,9 @@ from ..types_ import (
24
24
  BashCommand,
25
25
  BashInteraction,
26
26
  CreateFileNew,
27
- FullFileEdit,
27
+ FileEdit,
28
28
  ReadImage,
29
+ ReadFile,
29
30
  Writefile,
30
31
  ResetShell,
31
32
  )
@@ -58,7 +59,6 @@ from dotenv import load_dotenv
58
59
 
59
60
  class Config(BaseModel):
60
61
  model: Models
61
- secondary_model: Models
62
62
  cost_limit: float
63
63
  cost_file: dict[Models, CostData]
64
64
  cost_unit: str = "$"
@@ -146,10 +146,18 @@ def loop(
146
146
  waiting_for_assistant = history[-1]["role"] != "assistant"
147
147
 
148
148
  my_dir = os.path.dirname(__file__)
149
- config_file = os.path.join(my_dir, "..", "..", "..", "config.toml")
150
- with open(config_file) as f:
151
- config_json = toml.load(f)
152
- config = Config.model_validate(config_json)
149
+
150
+ config = Config(
151
+ model=cast(
152
+ Models, os.getenv("OPENAI_MODEL", "gpt-4o-2024-08-06").lower()),
153
+ cost_limit=0.1,
154
+ cost_unit="$",
155
+ cost_file={
156
+ "gpt-4o-2024-08-06": CostData(
157
+ cost_per_1m_input_tokens=5, cost_per_1m_output_tokens=15
158
+ ),
159
+ },
160
+ )
153
161
 
154
162
  if limit is not None:
155
163
  config.cost_limit = limit
@@ -178,6 +186,13 @@ def loop(
178
186
  - Special keys like arrows, interrupts, enter, etc.
179
187
  - Send text input to the running program.
180
188
  - Only one of send_text, send_specials, send_ascii should be provided.""",
189
+ ),
190
+ openai.pydantic_function_tool(
191
+ ReadFile,
192
+ description="""
193
+ - Read full file content
194
+ - Provide absolute file path only
195
+ """,
181
196
  ),
182
197
  openai.pydantic_function_tool(
183
198
  CreateFileNew,
@@ -185,14 +200,14 @@ def loop(
185
200
  - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
186
201
  - This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
187
202
  - Provide absolute file path only.
188
- - For editing existing files, use FullFileEdit.""",
203
+ - For editing existing files, use FileEdit.""",
189
204
  ),
190
205
  openai.pydantic_function_tool(
191
- FullFileEdit,
206
+ FileEdit,
192
207
  description="""
193
208
  - Use absolute file path only.
194
209
  - Use ONLY SEARCH/REPLACE blocks to edit the file.
195
- - file_edit_using_searh_replace_blocks should start with <<<<<<< SEARCH
210
+ - file_edit_using_search_replace_blocks should start with <<<<<<< SEARCH
196
211
  """,
197
212
  ),
198
213
  openai.pydantic_function_tool(
@@ -211,9 +226,9 @@ You're a cli assistant.
211
226
 
212
227
  Instructions:
213
228
 
214
- - You should use the provided bash execution tool to run script to complete objective.
215
- - Do not use sudo. Do not use interactive commands.
216
- - Ask user for confirmation before running anything major
229
+ - You should use the provided bash execution tool to run script to complete objective.
230
+ - First understand about the project by understanding the folder structure (ignoring node_modules or venv, etc.)
231
+ - Always read relevant files before making any changes.
217
232
 
218
233
  System information:
219
234
  - System: {uname_sysname}
@@ -9,6 +9,7 @@ import re
9
9
  import sys
10
10
  import threading
11
11
  import importlib.metadata
12
+ import time
12
13
  import traceback
13
14
  from typing import (
14
15
  Callable,
@@ -47,19 +48,17 @@ from openai.types.chat import (
47
48
  from nltk.metrics.distance import edit_distance
48
49
 
49
50
  from ..types_ import (
51
+ BashCommand,
52
+ BashInteraction,
50
53
  CreateFileNew,
51
54
  FileEditFindReplace,
52
- FullFileEdit,
55
+ FileEdit,
56
+ ReadFile,
57
+ ReadImage,
53
58
  ResetShell,
54
59
  Writefile,
55
60
  )
56
61
 
57
- from ..types_ import BashCommand
58
-
59
- from ..types_ import BashInteraction
60
-
61
- from ..types_ import ReadImage
62
-
63
62
  from .common import CostData, Models, discard_input
64
63
 
65
64
  from .openai_utils import get_input_cost, get_output_cost
@@ -319,7 +318,7 @@ def execute_bash(
319
318
  tokens = enc.encode(text)
320
319
 
321
320
  if max_tokens and len(tokens) >= max_tokens:
322
- text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
321
+ text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
323
322
 
324
323
  if is_interrupt:
325
324
  text = (
@@ -346,7 +345,7 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
346
345
 
347
346
  tokens = enc.encode(output)
348
347
  if max_tokens and len(tokens) >= max_tokens:
349
- output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
348
+ output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
350
349
 
351
350
  try:
352
351
  exit_status = get_status()
@@ -426,7 +425,7 @@ def read_image_from_shell(file_path: str) -> ImageData:
426
425
 
427
426
  def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
428
427
  if not os.path.isabs(writefile.file_path):
429
- return "Failure: file_path should be absolute path"
428
+ return f"Failure: file_path should be absolute path, current working directory is {CWD}"
430
429
  else:
431
430
  path_ = writefile.file_path
432
431
 
@@ -465,12 +464,14 @@ def find_least_edit_distance_substring(
465
464
  edit_distance_sum = 0
466
465
  for j in range(len(find_lines)):
467
466
  if (i + j) < len(content_lines):
468
- edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
467
+ edit_distance_sum += edit_distance(
468
+ content_lines[i + j], find_lines[j])
469
469
  else:
470
470
  edit_distance_sum += len(find_lines[j])
471
471
  if edit_distance_sum < min_edit_distance:
472
472
  min_edit_distance = edit_distance_sum
473
- min_edit_distance_lines = orig_content_lines[i : i + len(find_lines)]
473
+ min_edit_distance_lines = orig_content_lines[i: i + len(
474
+ find_lines)]
474
475
  return "\n".join(min_edit_distance_lines), min_edit_distance
475
476
 
476
477
 
@@ -494,11 +495,12 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
494
495
  return content
495
496
 
496
497
 
497
- def do_diff_edit(fedit: FullFileEdit) -> str:
498
+ def do_diff_edit(fedit: FileEdit) -> str:
498
499
  console.log(f"Editing file: {fedit.file_path}")
499
500
 
500
501
  if not os.path.isabs(fedit.file_path):
501
- raise Exception("Failure: file_path should be absolute path")
502
+ raise Exception(
503
+ f"Failure: file_path should be absolute path, current working directory is {CWD}")
502
504
  else:
503
505
  path_ = fedit.file_path
504
506
 
@@ -508,9 +510,10 @@ def do_diff_edit(fedit: FullFileEdit) -> str:
508
510
  with open(path_) as f:
509
511
  apply_diff_to = f.read()
510
512
 
511
- lines = fedit.file_edit_using_searh_replace_blocks.split("\n")
513
+ lines = fedit.file_edit_using_search_replace_blocks.split("\n")
512
514
  n_lines = len(lines)
513
515
  i = 0
516
+ replacement_count = 0
514
517
  while i < n_lines:
515
518
  if re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[i]):
516
519
  search_block = []
@@ -534,10 +537,17 @@ def do_diff_edit(fedit: FullFileEdit) -> str:
534
537
  search_block_ = "\n".join(search_block)
535
538
  replace_block_ = "\n".join(replace_block)
536
539
 
537
- apply_diff_to = edit_content(apply_diff_to, search_block_, replace_block_)
540
+ apply_diff_to = edit_content(
541
+ apply_diff_to, search_block_, replace_block_)
542
+ replacement_count += 1
538
543
  else:
539
544
  i += 1
540
545
 
546
+ if replacement_count == 0:
547
+ raise Exception(
548
+ "Error: no valid search-replace blocks found, please check your syntax for FileEdit"
549
+ )
550
+
541
551
  with open(path_, "w") as f:
542
552
  f.write(apply_diff_to)
543
553
 
@@ -546,7 +556,8 @@ def do_diff_edit(fedit: FullFileEdit) -> str:
546
556
 
547
557
  def file_edit(fedit: FileEditFindReplace) -> str:
548
558
  if not os.path.isabs(fedit.file_path):
549
- raise Exception("Failure: file_path should be absolute path")
559
+ raise Exception(
560
+ f"Failure: file_path should be absolute path, current working directory is {CWD}")
550
561
  else:
551
562
  path_ = fedit.file_path
552
563
 
@@ -556,14 +567,17 @@ def file_edit(fedit: FileEditFindReplace) -> str:
556
567
  if not fedit.find_lines:
557
568
  raise Exception("Error: `find_lines` cannot be empty")
558
569
 
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"))
570
+ out_string = "\n".join(
571
+ "> " + line for line in fedit.find_lines.split("\n"))
572
+ in_string = "\n".join(
573
+ "< " + line for line in fedit.replace_with_lines.split("\n"))
561
574
  console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
562
575
  try:
563
576
  with open(path_) as f:
564
577
  content = f.read()
565
578
 
566
- content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
579
+ content = edit_content(content, fedit.find_lines,
580
+ fedit.replace_with_lines)
567
581
 
568
582
  with open(path_, "w") as f:
569
583
  f.write(content)
@@ -603,10 +617,11 @@ TOOLS = (
603
617
  | Writefile
604
618
  | CreateFileNew
605
619
  | FileEditFindReplace
606
- | FullFileEdit
620
+ | FileEdit
607
621
  | AIAssistant
608
622
  | DoneFlag
609
623
  | ReadImage
624
+ | ReadFile
610
625
  )
611
626
 
612
627
 
@@ -630,14 +645,16 @@ def which_tool_name(name: str) -> Type[TOOLS]:
630
645
  return CreateFileNew
631
646
  elif name == "FileEditFindReplace":
632
647
  return FileEditFindReplace
633
- elif name == "FullFileEdit":
634
- return FullFileEdit
648
+ elif name == "FileEdit":
649
+ return FileEdit
635
650
  elif name == "AIAssistant":
636
651
  return AIAssistant
637
652
  elif name == "DoneFlag":
638
653
  return DoneFlag
639
654
  elif name == "ReadImage":
640
655
  return ReadImage
656
+ elif name == "ReadFile":
657
+ return ReadFile
641
658
  else:
642
659
  raise ValueError(f"Unknown tool name: {name}")
643
660
 
@@ -651,10 +668,11 @@ def get_tool_output(
651
668
  | Writefile
652
669
  | CreateFileNew
653
670
  | FileEditFindReplace
654
- | FullFileEdit
671
+ | FileEdit
655
672
  | AIAssistant
656
673
  | DoneFlag
657
- | ReadImage,
674
+ | ReadImage
675
+ | ReadFile,
658
676
  enc: tiktoken.Encoding,
659
677
  limit: float,
660
678
  loop_call: Callable[[str, float], tuple[str, float]],
@@ -669,10 +687,11 @@ def get_tool_output(
669
687
  | Writefile
670
688
  | CreateFileNew
671
689
  | FileEditFindReplace
672
- | FullFileEdit
690
+ | FileEdit
673
691
  | AIAssistant
674
692
  | DoneFlag
675
693
  | ReadImage
694
+ | ReadFile
676
695
  ](
677
696
  Confirmation
678
697
  | BashCommand
@@ -681,10 +700,11 @@ def get_tool_output(
681
700
  | Writefile
682
701
  | CreateFileNew
683
702
  | FileEditFindReplace
684
- | FullFileEdit
703
+ | FileEdit
685
704
  | AIAssistant
686
705
  | DoneFlag
687
706
  | ReadImage
707
+ | ReadFile
688
708
  )
689
709
  arg = adapter.validate_python(args)
690
710
  else:
@@ -705,7 +725,7 @@ def get_tool_output(
705
725
  elif isinstance(arg, FileEditFindReplace):
706
726
  console.print("Calling file edit tool")
707
727
  output = file_edit(arg), 0.0
708
- elif isinstance(arg, FullFileEdit):
728
+ elif isinstance(arg, FileEdit):
709
729
  console.print("Calling full file edit tool")
710
730
  output = do_diff_edit(arg), 0.0
711
731
  elif isinstance(arg, DoneFlag):
@@ -717,6 +737,9 @@ def get_tool_output(
717
737
  elif isinstance(arg, ReadImage):
718
738
  console.print("Calling read image tool")
719
739
  output = read_image_from_shell(arg.file_path), 0.0
740
+ elif isinstance(arg, ReadFile):
741
+ console.print("Calling read file tool")
742
+ output = read_file(arg), 0.0
720
743
  elif isinstance(arg, ResetShell):
721
744
  console.print("Calling reset shell tool")
722
745
  output = reset_shell(), 0.0
@@ -731,7 +754,8 @@ History = list[ChatCompletionMessageParam]
731
754
 
732
755
  default_enc = tiktoken.encoding_for_model("gpt-4o")
733
756
  default_model: Models = "gpt-4o-2024-08-06"
734
- default_cost = CostData(cost_per_1m_input_tokens=0.15, cost_per_1m_output_tokens=0.6)
757
+ default_cost = CostData(cost_per_1m_input_tokens=0.15,
758
+ cost_per_1m_output_tokens=0.6)
735
759
  curr_cost = 0.0
736
760
 
737
761
 
@@ -743,8 +767,9 @@ class Mdata(BaseModel):
743
767
  | CreateFileNew
744
768
  | ResetShell
745
769
  | FileEditFindReplace
746
- | FullFileEdit
770
+ | FileEdit
747
771
  | str
772
+ | ReadFile
748
773
  )
749
774
 
750
775
 
@@ -755,16 +780,16 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
755
780
  client_uuid = str(uuid.uuid4())
756
781
 
757
782
  # Create the WebSocket connection
758
- with syncconnect(f"{server_url}/{client_uuid}") as websocket:
759
- server_version = str(websocket.recv())
760
- print(f"Server version: {server_version}")
761
- client_version = importlib.metadata.version("wcgw")
762
- websocket.send(client_version)
763
-
764
- print(
765
- f"Connected. Share this user id with the chatbot: {client_uuid} \nLink: https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access"
766
- )
767
- try:
783
+ try:
784
+ with syncconnect(f"{server_url}/{client_uuid}") as websocket:
785
+ server_version = str(websocket.recv())
786
+ print(f"Server version: {server_version}")
787
+ client_version = importlib.metadata.version("wcgw")
788
+ websocket.send(client_version)
789
+
790
+ print(
791
+ f"Connected. Share this user id with the chatbot: {client_uuid} \nLink: https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access"
792
+ )
768
793
  while True:
769
794
  # Wait to receive data from the server
770
795
  message = websocket.recv()
@@ -773,7 +798,8 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
773
798
  raise Exception(mdata)
774
799
  try:
775
800
  output, cost = get_tool_output(
776
- mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
801
+ mdata.data, default_enc, 0.0, lambda x, y: (
802
+ "", 0), None
777
803
  )
778
804
  curr_cost += cost
779
805
  print(f"{curr_cost=}")
@@ -783,9 +809,10 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
783
809
  assert isinstance(output, str)
784
810
  websocket.send(output)
785
811
 
786
- except (websockets.ConnectionClosed, ConnectionError, OSError):
787
- print(f"Connection closed for UUID: {client_uuid}, retrying")
788
- register_client(server_url, client_uuid)
812
+ except (websockets.ConnectionClosed, ConnectionError, OSError):
813
+ print(f"Connection closed for UUID: {client_uuid}, retrying")
814
+ time.sleep(0.5)
815
+ register_client(server_url, client_uuid)
789
816
 
790
817
 
791
818
  run = Typer(pretty_exceptions_show_locals=False, no_args_is_help=True)
@@ -803,3 +830,19 @@ def app(
803
830
  exit()
804
831
 
805
832
  register_client(server_url, client_uuid or "")
833
+
834
+
835
+ def read_file(readfile: ReadFile) -> str:
836
+
837
+ console.print(f"Reading file: {readfile.file_path}")
838
+
839
+ if not os.path.isabs(readfile.file_path):
840
+ return f"Failure: file_path should be absolute path, current working directory is {CWD}"
841
+
842
+ path = Path(readfile.file_path)
843
+ if not path.exists():
844
+ return f"Error: file {readfile.file_path} does not exist"
845
+
846
+ with path.open("r") as f:
847
+ content = f.read()
848
+ return content
@@ -19,7 +19,8 @@ from ..types_ import (
19
19
  BashInteraction,
20
20
  CreateFileNew,
21
21
  FileEditFindReplace,
22
- FullFileEdit,
22
+ FileEdit,
23
+ ReadFile,
23
24
  ResetShell,
24
25
  Writefile,
25
26
  Specials,
@@ -34,7 +35,8 @@ class Mdata(BaseModel):
34
35
  | CreateFileNew
35
36
  | ResetShell
36
37
  | FileEditFindReplace
37
- | FullFileEdit
38
+ | FileEdit
39
+ | ReadFile
38
40
  | str
39
41
  )
40
42
  user_id: UUID
@@ -49,7 +51,7 @@ gpts: dict[UUID, Callable[[str], None]] = {}
49
51
  images: DefaultDict[UUID, dict[str, dict[str, Any]]] = DefaultDict(dict)
50
52
 
51
53
 
52
- CLIENT_VERSION_MINIMUM = "1.1.0"
54
+ CLIENT_VERSION_MINIMUM = "1.2.0"
53
55
 
54
56
 
55
57
  @app.websocket("/v1/register/{uuid}")
@@ -63,12 +65,14 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
63
65
  # receive client version
64
66
  client_version = await websocket.receive_text()
65
67
  sem_version_client = semantic_version.Version.coerce(client_version)
66
- sem_version_server = semantic_version.Version.coerce(CLIENT_VERSION_MINIMUM)
68
+ sem_version_server = semantic_version.Version.coerce(
69
+ CLIENT_VERSION_MINIMUM)
67
70
  if sem_version_client < sem_version_server:
68
71
  await websocket.send_text(
69
72
  Mdata(
70
73
  user_id=uuid,
71
- data=f"Client version {client_version} is outdated. Please upgrade to {CLIENT_VERSION_MINIMUM} or higher.",
74
+ data=f"Client version {client_version} is outdated. Please upgrade to {
75
+ CLIENT_VERSION_MINIMUM} or higher.",
72
76
  ).model_dump_json()
73
77
  )
74
78
  await websocket.close(
@@ -88,7 +92,8 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
88
92
  while True:
89
93
  received_data = await websocket.receive_text()
90
94
  if uuid not in gpts:
91
- raise fastapi.HTTPException(status_code=400, detail="No call made")
95
+ raise fastapi.HTTPException(
96
+ status_code=400, detail="No call made")
92
97
  gpts[uuid](received_data)
93
98
  except WebSocketDisconnect:
94
99
  # Remove the client if the WebSocket is disconnected
@@ -126,13 +131,13 @@ async def create_file(write_file_data: CreateFileNewWithUUID) -> str:
126
131
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
127
132
 
128
133
 
129
- class FullFileEditWithUUID(FullFileEdit):
134
+ class FileEditWithUUID(FileEdit):
130
135
  user_id: UUID
131
136
 
132
137
 
133
138
  @app.post("/v1/full_file_edit")
134
139
  async def file_edit_find_replace(
135
- file_edit_find_replace: FullFileEditWithUUID,
140
+ file_edit_find_replace: FileEditWithUUID,
136
141
  ) -> str:
137
142
  user_id = file_edit_find_replace.user_id
138
143
  if user_id not in clients:
@@ -257,6 +262,39 @@ async def bash_interaction(bash_interaction: BashInteractionWithUUID) -> str:
257
262
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
258
263
 
259
264
 
265
+ class ReadFileWithUUID(ReadFile):
266
+ user_id: UUID
267
+
268
+
269
+ @app.post("/v1/read_file")
270
+ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
271
+ user_id = read_file_data.user_id
272
+ if user_id not in clients:
273
+ return "Failure: id not found, ask the user to check it."
274
+
275
+ results: Optional[str] = None
276
+
277
+ def put_results(result: str) -> None:
278
+ nonlocal results
279
+ results = result
280
+
281
+ gpts[user_id] = put_results
282
+
283
+ await clients[user_id](
284
+ Mdata(data=ReadFile(file_path=read_file_data.file_path,
285
+ type=read_file_data.type
286
+ ), user_id=user_id)
287
+ )
288
+
289
+ start_time = time.time()
290
+ while time.time() - start_time < 30:
291
+ if results is not None:
292
+ return results
293
+ await asyncio.sleep(0.1)
294
+
295
+ raise fastapi.HTTPException(status_code=500, detail="Timeout error")
296
+
297
+
260
298
  app.mount("/static", StaticFiles(directory="static"), name="static")
261
299
 
262
300
 
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from typing import Literal, Optional, Sequence
2
3
  from pydantic import BaseModel
3
4
 
@@ -35,7 +36,7 @@ class BashInteraction(BaseModel):
35
36
 
36
37
  class ReadImage(BaseModel):
37
38
  file_path: str
38
- type: Literal["ReadImage"] = "ReadImage"
39
+ type: Literal["ReadImage"]
39
40
 
40
41
 
41
42
  class Writefile(BaseModel):
@@ -48,6 +49,11 @@ class CreateFileNew(BaseModel):
48
49
  file_content: str
49
50
 
50
51
 
52
+ class ReadFile(BaseModel):
53
+ file_path: str # The path to the file to read
54
+ type: Literal["ReadFile"]
55
+
56
+
51
57
  class FileEditFindReplace(BaseModel):
52
58
  file_path: str
53
59
  find_lines: str
@@ -58,6 +64,13 @@ class ResetShell(BaseModel):
58
64
  should_reset: Literal[True] = True
59
65
 
60
66
 
61
- class FullFileEdit(BaseModel):
67
+ class FileEdit(BaseModel):
62
68
  file_path: str
63
- file_edit_using_searh_replace_blocks: str
69
+ file_edit_using_search_replace_blocks: str
70
+
71
+ def model_post_init(self, __context: object) -> None:
72
+ # Ensure first line is "<<<<<<< SEARCH"
73
+
74
+ if not re.match(r"^<<<<<<+\s*SEARCH\s*$", self.file_edit_using_search_replace_blocks.split("\n")[0]):
75
+
76
+ raise ValueError("First line of file_edit_using_search_replace_blocks must be '<<<<<<< SEARCH'")
@@ -948,7 +948,7 @@ wheels = [
948
948
 
949
949
  [[package]]
950
950
  name = "wcgw"
951
- version = "1.1.0"
951
+ version = "1.2.0"
952
952
  source = { editable = "." }
953
953
  dependencies = [
954
954
  { name = "anthropic" },
wcgw-1.1.1/config.toml DELETED
@@ -1,11 +0,0 @@
1
- model = "gpt-4o-2024-08-06"
2
- secondary_model = 'gpt-4o-2024-08-06'
3
- cost_limit = 0.1
4
-
5
- [cost_file.gpt-4o-2024-08-06]
6
- cost_per_1m_input_tokens = 5
7
- cost_per_1m_output_tokens = 15
8
-
9
- [cost_file.gpt-4o-mini]
10
- cost_per_1m_input_tokens = 0.15
11
- cost_per_1m_output_tokens = 0.6
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes