wcgw 1.2.1__tar.gz → 1.3.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 (28) hide show
  1. {wcgw-1.2.1 → wcgw-1.3.0}/PKG-INFO +1 -1
  2. {wcgw-1.2.1 → wcgw-1.3.0}/gpt_action_json_schema.json +74 -2
  3. {wcgw-1.2.1 → wcgw-1.3.0}/gpt_instructions.txt +31 -8
  4. {wcgw-1.2.1 → wcgw-1.3.0}/pyproject.toml +1 -1
  5. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/anthropic_client.py +10 -7
  6. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/diff-instructions.txt +28 -4
  7. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/openai_client.py +11 -9
  8. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/tools.py +123 -71
  9. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/relay/serve.py +35 -6
  10. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/types_.py +4 -22
  11. {wcgw-1.2.1 → wcgw-1.3.0}/.github/workflows/python-publish.yml +0 -0
  12. {wcgw-1.2.1 → wcgw-1.3.0}/.github/workflows/python-tests.yml +0 -0
  13. {wcgw-1.2.1 → wcgw-1.3.0}/.gitignore +0 -0
  14. {wcgw-1.2.1 → wcgw-1.3.0}/.python-version +0 -0
  15. {wcgw-1.2.1 → wcgw-1.3.0}/.vscode/settings.json +0 -0
  16. {wcgw-1.2.1 → wcgw-1.3.0}/README.md +0 -0
  17. {wcgw-1.2.1 → wcgw-1.3.0}/src/__init__.py +0 -0
  18. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/__init__.py +0 -0
  19. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/__init__.py +0 -0
  20. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/__main__.py +0 -0
  21. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/cli.py +0 -0
  22. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/common.py +0 -0
  23. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/client/openai_utils.py +0 -0
  24. {wcgw-1.2.1 → wcgw-1.3.0}/src/wcgw/relay/static/privacy.txt +0 -0
  25. {wcgw-1.2.1 → wcgw-1.3.0}/static/ss1.png +0 -0
  26. {wcgw-1.2.1 → wcgw-1.3.0}/tests/test_basic.py +0 -0
  27. {wcgw-1.2.1 → wcgw-1.3.0}/tests/test_tools.py +0 -0
  28. {wcgw-1.2.1 → wcgw-1.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 1.2.1
3
+ Version: 1.3.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>
@@ -249,12 +249,60 @@
249
249
  }
250
250
  }
251
251
  }
252
+ },
253
+ "/v1/initialize": {
254
+ "post": {
255
+ "x-openai-isConsequential": false,
256
+ "summary": "Initialize",
257
+ "operationId": "initialize_v1_initialize_post",
258
+ "requestBody": {
259
+ "content": {
260
+ "application/json": {
261
+ "schema": {
262
+ "$ref": "#/components/schemas/InitializeWithUUID"
263
+ }
264
+ }
265
+ },
266
+ "required": true
267
+ },
268
+ "responses": {
269
+ "200": {
270
+ "description": "Successful Response",
271
+ "content": {
272
+ "application/json": {
273
+ "schema": {
274
+ "type": "string",
275
+ "title": "Response Initialize V1 Initialize Post"
276
+ }
277
+ }
278
+ }
279
+ },
280
+ "422": {
281
+ "description": "Validation Error",
282
+ "content": {
283
+ "application/json": {
284
+ "schema": {
285
+ "$ref": "#/components/schemas/HTTPValidationError"
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
252
292
  }
253
293
  },
254
294
  "components": {
255
295
  "schemas": {
256
296
  "BashInteractionWithUUID": {
257
297
  "properties": {
298
+ "type": {
299
+ "type": "string",
300
+ "enum": [
301
+ "BashInteraction"
302
+ ],
303
+ "const": "BashInteraction",
304
+ "title": "Type"
305
+ },
258
306
  "send_text": {
259
307
  "anyOf": [
260
308
  {
@@ -312,6 +360,7 @@
312
360
  },
313
361
  "type": "object",
314
362
  "required": [
363
+ "type",
315
364
  "user_id"
316
365
  ],
317
366
  "title": "BashInteractionWithUUID"
@@ -396,6 +445,29 @@
396
445
  "type": "object",
397
446
  "title": "HTTPValidationError"
398
447
  },
448
+ "InitializeWithUUID": {
449
+ "properties": {
450
+ "type": {
451
+ "type": "string",
452
+ "enum": [
453
+ "Initialize"
454
+ ],
455
+ "const": "Initialize",
456
+ "title": "Type"
457
+ },
458
+ "user_id": {
459
+ "type": "string",
460
+ "format": "uuid",
461
+ "title": "User Id"
462
+ }
463
+ },
464
+ "type": "object",
465
+ "required": [
466
+ "type",
467
+ "user_id"
468
+ ],
469
+ "title": "InitializeWithUUID"
470
+ },
399
471
  "ReadFileWithUUID": {
400
472
  "properties": {
401
473
  "file_path": {
@@ -432,8 +504,7 @@
432
504
  true
433
505
  ],
434
506
  "const": true,
435
- "title": "Should Reset",
436
- "default": true
507
+ "title": "Should Reset"
437
508
  },
438
509
  "user_id": {
439
510
  "type": "string",
@@ -443,6 +514,7 @@
443
514
  },
444
515
  "type": "object",
445
516
  "required": [
517
+ "should_reset",
446
518
  "user_id"
447
519
  ],
448
520
  "title": "ResetShellWithUUID"
@@ -1,12 +1,14 @@
1
1
  You're an expert software engineer with shell and code knowledge.
2
2
 
3
3
  Instructions:
4
-
4
+
5
5
  - You should use the provided bash execution, reading and writing file tools to complete objective.
6
6
  - First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
7
7
  - Always read relevant files before editing.
8
8
  - Do not provide code snippets unless asked by the user, instead directly edit the code.
9
9
 
10
+ Instructions for `Initialize`:
11
+ - Always call this at the start of the conversation.
10
12
 
11
13
  Instructions for `BashCommand`:
12
14
  - Execute a bash command. This is stateful (beware with subsequent calls).
@@ -14,6 +16,7 @@ Instructions for `BashCommand`:
14
16
  - Status of the command and the current working directory will always be returned at the end.
15
17
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
16
18
  - The first line might be `(...truncated)` if the output is too long.
19
+ - The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
17
20
 
18
21
  Instructions for `Read File`
19
22
  - Read full content of a file.
@@ -39,16 +42,35 @@ Instructions for `FileEdit`:
39
42
  - Use absolute file path only.
40
43
  - Use SEARCH/REPLACE blocks to edit the file.
41
44
  Only edit the files using the following SEARCH/REPLACE blocks.
45
+
42
46
  ```
43
47
  <<<<<<< SEARCH
44
- // Original code
48
+ def hello():
49
+ "print a greeting"
50
+
51
+ print("hello")
52
+ =======
53
+ from hello import hello as hello_renamed
54
+ >>>>>>> REPLACE
55
+ <<<<<<< SEARCH
56
+ def call_hello():
57
+ "call hello"
58
+
59
+ hello()
45
60
  =======
46
- // New code
61
+ def call_hello_renamed():
62
+ "call hello renamed"
63
+
64
+ hello_renamed()
47
65
  >>>>>>> REPLACE
48
66
  <<<<<<< SEARCH
49
- // Original code further down in the file
67
+ impl1()
68
+ hello()
69
+ impl2()
50
70
  =======
51
- // New code to replace
71
+ impl1()
72
+ hello_renamed()
73
+ impl2()
52
74
  >>>>>>> REPLACE
53
75
  ```
54
76
 
@@ -61,7 +83,7 @@ Instructions for `FileEdit`:
61
83
  4. The lines to replace into the source code
62
84
  5. The end of the replace block: >>>>>>> REPLACE
63
85
 
64
- Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
86
+ Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, whitespaces etc.
65
87
 
66
88
  *SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
67
89
  Including multiple unique *SEARCH/REPLACE* blocks if needed.
@@ -72,11 +94,12 @@ Instructions for `FileEdit`:
72
94
  Include just the changing lines, and a few surrounding lines if needed for uniqueness.
73
95
  Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
74
96
 
75
- 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.
97
+ Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.
76
98
 
77
99
  ---
78
100
  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.
79
101
 
80
102
  Always write production ready, syntactically correct code.
81
103
  ---
82
- Ask the user for the user_id `UUID` if they haven't provided in the first message.
104
+ - Ask the user for the user_id `UUID` if they haven't provided in the first message.
105
+ - Call "Initialize" as soon as you get the UUID.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
3
3
  name = "wcgw"
4
- version = "1.2.1"
4
+ version = "1.3.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"
@@ -165,6 +165,7 @@ def loop(
165
165
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
166
166
  - The first line might be `(...truncated)` if the output is too long.
167
167
  - Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
168
+ - The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
168
169
  """,
169
170
  ),
170
171
  ToolParam(
@@ -193,7 +194,7 @@ def loop(
193
194
  - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
194
195
  - This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
195
196
  - Provide absolute file path only.
196
- - For editing existing files, use FileEdit.
197
+ - For editing existing files, use FileEdit instead of this tool.
197
198
  """,
198
199
  ),
199
200
  ToolParam(
@@ -219,17 +220,19 @@ def loop(
219
220
  uname_machine = os.uname().machine
220
221
 
221
222
  system = f"""
222
- You're a cli assistant.
223
+ You're an expert software engineer with shell and code knowledge.
223
224
 
224
225
  Instructions:
225
226
 
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.
229
-
227
+ - You should use the provided bash execution, reading and writing file tools to complete objective.
228
+ - First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
229
+ - Always read relevant files before editing.
230
+ - Do not provide code snippets unless asked by the user, instead directly edit the code.
231
+
230
232
  System information:
231
233
  - System: {uname_sysname}
232
234
  - Machine: {uname_machine}
235
+ - Current directory: {os.getcwd()}
233
236
  """
234
237
 
235
238
  with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f:
@@ -360,7 +363,7 @@ System information:
360
363
  enc,
361
364
  limit - cost,
362
365
  loop,
363
- max_tokens=8096,
366
+ max_tokens=8000,
364
367
  )
365
368
  except Exception as e:
366
369
  output_or_done = (
@@ -1,15 +1,39 @@
1
1
 
2
- Instructions for
2
+ Instructions for editing files.
3
+
4
+
3
5
  Only edit the files using the following SEARCH/REPLACE blocks.
4
6
  ```
7
+ file_edit_using_search_replace_blocks="""
5
8
  <<<<<<< SEARCH
6
9
  def hello():
7
10
  "print a greeting"
8
11
 
9
12
  print("hello")
10
13
  =======
11
- from hello import hello
14
+ from hello import hello as hello_renamed
15
+ >>>>>>> REPLACE
16
+ <<<<<<< SEARCH
17
+ def call_hello():
18
+ "call hello"
19
+
20
+ hello()
21
+ =======
22
+ def call_hello_renamed():
23
+ "call hello renamed"
24
+
25
+ hello_renamed()
26
+ >>>>>>> REPLACE
27
+ <<<<<<< SEARCH
28
+ impl1()
29
+ hello()
30
+ impl2()
31
+ =======
32
+ impl1()
33
+ hello_renamed()
34
+ impl2()
12
35
  >>>>>>> REPLACE
36
+ """
13
37
  ```
14
38
 
15
39
  # *SEARCH/REPLACE block* Rules:
@@ -21,7 +45,7 @@ Every *SEARCH/REPLACE block* must use this format:
21
45
  4. The lines to replace into the source code
22
46
  5. The end of the replace block: >>>>>>> REPLACE
23
47
 
24
- Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
48
+ Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, whitespaces, etc.
25
49
 
26
50
  *SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
27
51
  Including multiple unique *SEARCH/REPLACE* blocks if needed.
@@ -32,4 +56,4 @@ Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each ch
32
56
  Include just the changing lines, and a few surrounding lines if needed for uniqueness.
33
57
  Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
34
58
 
35
- Include context lines before and after the code to edit for better matching in "<<<<<<< SEARCH". Recommended to add 3 lines on each side.
59
+ Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.
@@ -148,8 +148,7 @@ def loop(
148
148
  my_dir = os.path.dirname(__file__)
149
149
 
150
150
  config = Config(
151
- model=cast(
152
- Models, os.getenv("OPENAI_MODEL", "gpt-4o-2024-08-06").lower()),
151
+ model=cast(Models, os.getenv("OPENAI_MODEL", "gpt-4o-2024-08-06").lower()),
153
152
  cost_limit=0.1,
154
153
  cost_unit="$",
155
154
  cost_file={
@@ -177,6 +176,7 @@ def loop(
177
176
  - Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
178
177
  - The first line might be `(...truncated)` if the output is too long.
179
178
  - Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
179
+ - The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
180
180
  """,
181
181
  ),
182
182
  openai.pydantic_function_tool(
@@ -201,7 +201,7 @@ def loop(
201
201
  - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
202
202
  - This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
203
203
  - Provide absolute file path only.
204
- - For editing existing files, use FileEdit.""",
204
+ - For editing existing files, use FileEdit instead of this tool.""",
205
205
  ),
206
206
  openai.pydantic_function_tool(
207
207
  FileEdit,
@@ -223,17 +223,19 @@ def loop(
223
223
  uname_machine = os.uname().machine
224
224
 
225
225
  system = f"""
226
- You're a cli assistant.
226
+ You're an expert software engineer with shell and code knowledge.
227
227
 
228
228
  Instructions:
229
229
 
230
- - You should use the provided bash execution tool to run script to complete objective.
231
- - First understand about the project by understanding the folder structure (ignoring node_modules or venv, etc.)
232
- - Always read relevant files before making any changes.
233
-
230
+ - You should use the provided bash execution, reading and writing file tools to complete objective.
231
+ - First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
232
+ - Always read relevant files before editing.
233
+ - Do not provide code snippets unless asked by the user, instead directly edit the code.
234
+
234
235
  System information:
235
236
  - System: {uname_sysname}
236
237
  - Machine: {uname_machine}
238
+ - Current directory: {os.getcwd()}
237
239
 
238
240
  """
239
241
 
@@ -340,7 +342,7 @@ System information:
340
342
  enc,
341
343
  limit - cost,
342
344
  loop,
343
- max_tokens=2048,
345
+ max_tokens=8000,
344
346
  )
345
347
  except Exception as e:
346
348
  output_or_done = (
@@ -45,7 +45,7 @@ from openai.types.chat import (
45
45
  ChatCompletionMessage,
46
46
  ParsedChatCompletionMessage,
47
47
  )
48
- from nltk.metrics.distance import edit_distance
48
+ from nltk.metrics.distance import edit_distance # type: ignore[import-untyped]
49
49
 
50
50
  from ..types_ import (
51
51
  BashCommand,
@@ -53,6 +53,7 @@ from ..types_ import (
53
53
  CreateFileNew,
54
54
  FileEditFindReplace,
55
55
  FileEdit,
56
+ Initialize,
56
57
  ReadFile,
57
58
  ReadImage,
58
59
  ResetShell,
@@ -155,6 +156,16 @@ BASH_STATE: BASH_CLF_OUTPUT = "repl"
155
156
  CWD = os.getcwd()
156
157
 
157
158
 
159
+ def initial_info() -> str:
160
+ uname_sysname = os.uname().sysname
161
+ uname_machine = os.uname().machine
162
+ return f"""
163
+ System: {uname_sysname}
164
+ Machine: {uname_machine}
165
+ Current working directory: {CWD}
166
+ """
167
+
168
+
158
169
  def reset_shell() -> str:
159
170
  global SHELL, BASH_STATE, CWD
160
171
  SHELL.close(True)
@@ -251,55 +262,69 @@ def execute_bash(
251
262
  )
252
263
 
253
264
  SHELL.sendline(command)
254
- elif bash_arg.send_specials:
255
- console.print(f"Sending special sequence: {bash_arg.send_specials}")
256
- for char in bash_arg.send_specials:
257
- if char == "Key-up":
258
- SHELL.send("\033[A")
259
- elif char == "Key-down":
260
- SHELL.send("\033[B")
261
- elif char == "Key-left":
262
- SHELL.send("\033[D")
263
- elif char == "Key-right":
264
- SHELL.send("\033[C")
265
- elif char == "Enter":
266
- SHELL.send("\n")
267
- elif char == "Ctrl-c":
268
- SHELL.sendintr()
269
- is_interrupt = True
270
- elif char == "Ctrl-d":
271
- SHELL.sendintr()
272
- is_interrupt = True
273
- elif char == "Ctrl-z":
274
- SHELL.send("\x1a")
275
- else:
276
- raise Exception(f"Unknown special character: {char}")
277
- elif bash_arg.send_ascii:
278
- console.print(f"Sending ASCII sequence: {bash_arg.send_ascii}")
279
- for ascii_char in bash_arg.send_ascii:
280
- SHELL.send(chr(ascii_char))
281
- if ascii_char == 3:
282
- is_interrupt = True
265
+
283
266
  else:
284
- if bash_arg.send_text is None:
267
+ if (
268
+ sum(
269
+ [
270
+ int(bool(bash_arg.send_text)),
271
+ int(bool(bash_arg.send_specials)),
272
+ int(bool(bash_arg.send_ascii)),
273
+ ]
274
+ )
275
+ != 1
276
+ ):
285
277
  return (
286
- "Failure: at least one of send_text, send_specials or send_ascii should be provided",
278
+ "Failure: exactly one of send_text, send_specials or send_ascii should be provided",
287
279
  0.0,
288
280
  )
281
+ if bash_arg.send_specials:
282
+ console.print(f"Sending special sequence: {bash_arg.send_specials}")
283
+ for char in bash_arg.send_specials:
284
+ if char == "Key-up":
285
+ SHELL.send("\033[A")
286
+ elif char == "Key-down":
287
+ SHELL.send("\033[B")
288
+ elif char == "Key-left":
289
+ SHELL.send("\033[D")
290
+ elif char == "Key-right":
291
+ SHELL.send("\033[C")
292
+ elif char == "Enter":
293
+ SHELL.send("\n")
294
+ elif char == "Ctrl-c":
295
+ SHELL.sendintr()
296
+ is_interrupt = True
297
+ elif char == "Ctrl-d":
298
+ SHELL.sendintr()
299
+ is_interrupt = True
300
+ elif char == "Ctrl-z":
301
+ SHELL.send("\x1a")
302
+ else:
303
+ raise Exception(f"Unknown special character: {char}")
304
+ elif bash_arg.send_ascii:
305
+ console.print(f"Sending ASCII sequence: {bash_arg.send_ascii}")
306
+ for ascii_char in bash_arg.send_ascii:
307
+ SHELL.send(chr(ascii_char))
308
+ if ascii_char == 3:
309
+ is_interrupt = True
310
+ else:
311
+ if bash_arg.send_text is None:
312
+ return (
313
+ "Failure: at least one of send_text, send_specials or send_ascii should be provided",
314
+ 0.0,
315
+ )
289
316
 
290
- updated_repl_mode = update_repl_prompt(bash_arg.send_text)
291
- if updated_repl_mode:
292
- BASH_STATE = "repl"
293
- response = (
294
- "Prompt updated, you can execute REPL lines using BashCommand now"
295
- )
296
- console.print(response)
297
- return (
298
- response,
299
- 0,
300
- )
301
- console.print(f"Interact text: {bash_arg.send_text}")
302
- SHELL.sendline(bash_arg.send_text)
317
+ updated_repl_mode = update_repl_prompt(bash_arg.send_text)
318
+ if updated_repl_mode:
319
+ BASH_STATE = "repl"
320
+ response = "Prompt updated, you can execute REPL lines using BashCommand now"
321
+ console.print(response)
322
+ return (
323
+ response,
324
+ 0,
325
+ )
326
+ console.print(f"Interact text: {bash_arg.send_text}")
327
+ SHELL.sendline(bash_arg.send_text)
303
328
 
304
329
  BASH_STATE = "repl"
305
330
 
@@ -318,7 +343,7 @@ def execute_bash(
318
343
  tokens = enc.encode(text)
319
344
 
320
345
  if max_tokens and len(tokens) >= max_tokens:
321
- text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
346
+ text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
322
347
 
323
348
  if is_interrupt:
324
349
  text = (
@@ -345,7 +370,7 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
345
370
 
346
371
  tokens = enc.encode(output)
347
372
  if max_tokens and len(tokens) >= max_tokens:
348
- output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
373
+ output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
349
374
 
350
375
  try:
351
376
  exit_status = get_status()
@@ -420,7 +445,7 @@ def read_image_from_shell(file_path: str) -> ImageData:
420
445
  image_bytes = image_file.read()
421
446
  image_b64 = base64.b64encode(image_bytes).decode("utf-8")
422
447
  image_type = mimetypes.guess_type(file_path)[0]
423
- return ImageData(media_type=image_type, data=image_b64)
448
+ return ImageData(media_type=image_type, data=image_b64) # type: ignore
424
449
 
425
450
 
426
451
  def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
@@ -472,15 +497,21 @@ def find_least_edit_distance_substring(
472
497
  edit_distance_sum = 0
473
498
  for j in range(len(find_lines)):
474
499
  if (i + j) < len(content_lines):
475
- edit_distance_sum += edit_distance(
476
- content_lines[i + j], find_lines[j])
500
+ edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
477
501
  else:
478
502
  edit_distance_sum += len(find_lines[j])
479
503
  if edit_distance_sum < min_edit_distance:
480
504
  min_edit_distance = edit_distance_sum
481
505
  orig_start_index = new_to_original_indices[i]
482
- orig_end_index = new_to_original_indices.get(i + len(find_lines) - 1, len(orig_content_lines) - 1) + 1
483
- min_edit_distance_lines = orig_content_lines[orig_start_index:orig_end_index]
506
+ orig_end_index = (
507
+ new_to_original_indices.get(
508
+ i + len(find_lines) - 1, len(orig_content_lines) - 1
509
+ )
510
+ + 1
511
+ )
512
+ min_edit_distance_lines = orig_content_lines[
513
+ orig_start_index:orig_end_index
514
+ ]
484
515
  return "\n".join(min_edit_distance_lines), min_edit_distance
485
516
 
486
517
 
@@ -509,7 +540,8 @@ def do_diff_edit(fedit: FileEdit) -> str:
509
540
 
510
541
  if not os.path.isabs(fedit.file_path):
511
542
  raise Exception(
512
- f"Failure: file_path should be absolute path, current working directory is {CWD}")
543
+ f"Failure: file_path should be absolute path, current working directory is {CWD}"
544
+ )
513
545
  else:
514
546
  path_ = fedit.file_path
515
547
 
@@ -519,7 +551,16 @@ def do_diff_edit(fedit: FileEdit) -> str:
519
551
  with open(path_) as f:
520
552
  apply_diff_to = f.read()
521
553
 
554
+ fedit.file_edit_using_search_replace_blocks = (
555
+ fedit.file_edit_using_search_replace_blocks.strip()
556
+ )
522
557
  lines = fedit.file_edit_using_search_replace_blocks.split("\n")
558
+
559
+ if not lines or not re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[0]):
560
+ raise Exception(
561
+ "Error: first line should be `<<<<<< SEARCH` to start a search-replace block"
562
+ )
563
+
523
564
  n_lines = len(lines)
524
565
  i = 0
525
566
  replacement_count = 0
@@ -539,15 +580,14 @@ def do_diff_edit(fedit: FileEdit) -> str:
539
580
 
540
581
  for line in search_block:
541
582
  console.log("> " + line)
542
- console.log("---")
583
+ console.log("=======")
543
584
  for line in replace_block:
544
585
  console.log("< " + line)
545
-
586
+ console.log("\n\n\n\n")
546
587
  search_block_ = "\n".join(search_block)
547
588
  replace_block_ = "\n".join(replace_block)
548
589
 
549
- apply_diff_to = edit_content(
550
- apply_diff_to, search_block_, replace_block_)
590
+ apply_diff_to = edit_content(apply_diff_to, search_block_, replace_block_)
551
591
  replacement_count += 1
552
592
  else:
553
593
  i += 1
@@ -566,7 +606,8 @@ def do_diff_edit(fedit: FileEdit) -> str:
566
606
  def file_edit(fedit: FileEditFindReplace) -> str:
567
607
  if not os.path.isabs(fedit.file_path):
568
608
  raise Exception(
569
- f"Failure: file_path should be absolute path, current working directory is {CWD}")
609
+ f"Failure: file_path should be absolute path, current working directory is {CWD}"
610
+ )
570
611
  else:
571
612
  path_ = fedit.file_path
572
613
 
@@ -576,17 +617,14 @@ def file_edit(fedit: FileEditFindReplace) -> str:
576
617
  if not fedit.find_lines:
577
618
  raise Exception("Error: `find_lines` cannot be empty")
578
619
 
579
- out_string = "\n".join(
580
- "> " + line for line in fedit.find_lines.split("\n"))
581
- in_string = "\n".join(
582
- "< " + line for line in fedit.replace_with_lines.split("\n"))
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"))
583
622
  console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
584
623
  try:
585
624
  with open(path_) as f:
586
625
  content = f.read()
587
626
 
588
- content = edit_content(content, fedit.find_lines,
589
- fedit.replace_with_lines)
627
+ content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
590
628
 
591
629
  with open(path_, "w") as f:
592
630
  f.write(content)
@@ -631,6 +669,7 @@ TOOLS = (
631
669
  | DoneFlag
632
670
  | ReadImage
633
671
  | ReadFile
672
+ | Initialize
634
673
  )
635
674
 
636
675
 
@@ -664,6 +703,8 @@ def which_tool_name(name: str) -> Type[TOOLS]:
664
703
  return ReadImage
665
704
  elif name == "ReadFile":
666
705
  return ReadFile
706
+ elif name == "Initialize":
707
+ return Initialize
667
708
  else:
668
709
  raise ValueError(f"Unknown tool name: {name}")
669
710
 
@@ -681,6 +722,7 @@ def get_tool_output(
681
722
  | AIAssistant
682
723
  | DoneFlag
683
724
  | ReadImage
725
+ | Initialize
684
726
  | ReadFile,
685
727
  enc: tiktoken.Encoding,
686
728
  limit: float,
@@ -701,6 +743,7 @@ def get_tool_output(
701
743
  | DoneFlag
702
744
  | ReadImage
703
745
  | ReadFile
746
+ | Initialize
704
747
  ](
705
748
  Confirmation
706
749
  | BashCommand
@@ -714,6 +757,7 @@ def get_tool_output(
714
757
  | DoneFlag
715
758
  | ReadImage
716
759
  | ReadFile
760
+ | Initialize
717
761
  )
718
762
  arg = adapter.validate_python(args)
719
763
  else:
@@ -748,10 +792,13 @@ def get_tool_output(
748
792
  output = read_image_from_shell(arg.file_path), 0.0
749
793
  elif isinstance(arg, ReadFile):
750
794
  console.print("Calling read file tool")
751
- output = read_file(arg), 0.0
795
+ output = read_file(arg, max_tokens), 0.0
752
796
  elif isinstance(arg, ResetShell):
753
797
  console.print("Calling reset shell tool")
754
798
  output = reset_shell(), 0.0
799
+ elif isinstance(arg, Initialize):
800
+ console.print("Calling initial info tool")
801
+ output = initial_info(), 0.0
755
802
  else:
756
803
  raise ValueError(f"Unknown tool: {arg}")
757
804
 
@@ -763,8 +810,7 @@ History = list[ChatCompletionMessageParam]
763
810
 
764
811
  default_enc = tiktoken.encoding_for_model("gpt-4o")
765
812
  default_model: Models = "gpt-4o-2024-08-06"
766
- default_cost = CostData(cost_per_1m_input_tokens=0.15,
767
- cost_per_1m_output_tokens=0.6)
813
+ default_cost = CostData(cost_per_1m_input_tokens=0.15, cost_per_1m_output_tokens=0.6)
768
814
  curr_cost = 0.0
769
815
 
770
816
 
@@ -779,6 +825,7 @@ class Mdata(BaseModel):
779
825
  | FileEdit
780
826
  | str
781
827
  | ReadFile
828
+ | Initialize
782
829
  )
783
830
 
784
831
 
@@ -807,8 +854,7 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
807
854
  raise Exception(mdata)
808
855
  try:
809
856
  output, cost = get_tool_output(
810
- mdata.data, default_enc, 0.0, lambda x, y: (
811
- "", 0), None
857
+ mdata.data, default_enc, 0.0, lambda x, y: ("", 0), 8000
812
858
  )
813
859
  curr_cost += cost
814
860
  print(f"{curr_cost=}")
@@ -841,8 +887,7 @@ def app(
841
887
  register_client(server_url, client_uuid or "")
842
888
 
843
889
 
844
- def read_file(readfile: ReadFile) -> str:
845
-
890
+ def read_file(readfile: ReadFile, max_tokens: Optional[int]) -> str:
846
891
  console.print(f"Reading file: {readfile.file_path}")
847
892
 
848
893
  if not os.path.isabs(readfile.file_path):
@@ -854,4 +899,11 @@ def read_file(readfile: ReadFile) -> str:
854
899
 
855
900
  with path.open("r") as f:
856
901
  content = f.read()
902
+
903
+ if max_tokens is not None:
904
+ tokens = default_enc.encode(content)
905
+ if len(tokens) > max_tokens:
906
+ content = default_enc.decode(tokens[: max_tokens - 5])
907
+ content += "\n...(truncated)"
908
+
857
909
  return content
@@ -20,6 +20,7 @@ from ..types_ import (
20
20
  CreateFileNew,
21
21
  FileEditFindReplace,
22
22
  FileEdit,
23
+ Initialize,
23
24
  ReadFile,
24
25
  ResetShell,
25
26
  Writefile,
@@ -37,6 +38,7 @@ class Mdata(BaseModel):
37
38
  | FileEditFindReplace
38
39
  | FileEdit
39
40
  | ReadFile
41
+ | Initialize
40
42
  | str
41
43
  )
42
44
  user_id: UUID
@@ -51,7 +53,7 @@ gpts: dict[UUID, Callable[[str], None]] = {}
51
53
  images: DefaultDict[UUID, dict[str, dict[str, Any]]] = DefaultDict(dict)
52
54
 
53
55
 
54
- CLIENT_VERSION_MINIMUM = "1.2.0"
56
+ CLIENT_VERSION_MINIMUM = "1.3.0"
55
57
 
56
58
 
57
59
  @app.websocket("/v1/register/{uuid}")
@@ -71,8 +73,7 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
71
73
  await websocket.send_text(
72
74
  Mdata(
73
75
  user_id=uuid,
74
- data=f"Client version {client_version} is outdated. Please upgrade to {
75
- CLIENT_VERSION_MINIMUM} or higher.",
76
+ data=f"Client version {client_version} is outdated. Please upgrade to {CLIENT_VERSION_MINIMUM} or higher.",
76
77
  ).model_dump_json()
77
78
  )
78
79
  await websocket.close(
@@ -281,9 +282,37 @@ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
281
282
  gpts[user_id] = put_results
282
283
 
283
284
  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)
285
+ Mdata(data=read_file_data, user_id=user_id)
286
+ )
287
+
288
+ start_time = time.time()
289
+ while time.time() - start_time < 30:
290
+ if results is not None:
291
+ return results
292
+ await asyncio.sleep(0.1)
293
+
294
+ raise fastapi.HTTPException(status_code=500, detail="Timeout error")
295
+
296
+ class InitializeWithUUID(Initialize):
297
+ user_id: UUID
298
+
299
+
300
+ @app.post("/v1/initialize")
301
+ async def initialize(initialize_data: InitializeWithUUID) -> str:
302
+ user_id = initialize_data.user_id
303
+ if user_id not in clients:
304
+ return "Failure: id not found, ask the user to check it."
305
+
306
+ results: Optional[str] = None
307
+
308
+ def put_results(result: str) -> None:
309
+ nonlocal results
310
+ results = result
311
+
312
+ gpts[user_id] = put_results
313
+
314
+ await clients[user_id](
315
+ Mdata(data=initialize_data, user_id=user_id)
287
316
  )
288
317
 
289
318
  start_time = time.time()
@@ -13,26 +13,11 @@ Specials = Literal[
13
13
 
14
14
 
15
15
  class BashInteraction(BaseModel):
16
+ type: Literal["BashInteraction"]
16
17
  send_text: Optional[str] = None
17
18
  send_specials: Optional[Sequence[Specials]] = None
18
19
  send_ascii: Optional[Sequence[int]] = None
19
20
 
20
- def model_post_init(self, __context: object) -> None:
21
- # Ensure only one of the fields is set
22
- if (
23
- sum(
24
- [
25
- int(bool(self.send_text)),
26
- int(bool(self.send_specials)),
27
- int(bool(self.send_ascii)),
28
- ]
29
- )
30
- != 1
31
- ):
32
- raise ValueError(
33
- "Exactly one of 'send_text', 'send_specials', or 'send_ascii' must be set"
34
- )
35
-
36
21
 
37
22
  class ReadImage(BaseModel):
38
23
  file_path: str
@@ -61,16 +46,13 @@ class FileEditFindReplace(BaseModel):
61
46
 
62
47
 
63
48
  class ResetShell(BaseModel):
64
- should_reset: Literal[True] = True
49
+ should_reset: Literal[True]
65
50
 
66
51
 
67
52
  class FileEdit(BaseModel):
68
53
  file_path: str
69
54
  file_edit_using_search_replace_blocks: str
70
55
 
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
56
 
76
- raise ValueError("First line of file_edit_using_search_replace_blocks must be '<<<<<<< SEARCH'")
57
+ class Initialize(BaseModel):
58
+ type: Literal["Initialize"]
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
File without changes