wcgw 1.2.2__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.2 → wcgw-1.3.0}/PKG-INFO +1 -1
  2. {wcgw-1.2.2 → wcgw-1.3.0}/gpt_action_json_schema.json +68 -6
  3. {wcgw-1.2.2 → wcgw-1.3.0}/gpt_instructions.txt +9 -3
  4. {wcgw-1.2.2 → wcgw-1.3.0}/pyproject.toml +1 -1
  5. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/anthropic_client.py +6 -4
  6. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/diff-instructions.txt +5 -1
  7. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/openai_client.py +6 -5
  8. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/tools.py +61 -31
  9. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/relay/serve.py +35 -6
  10. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/types_.py +4 -0
  11. {wcgw-1.2.2 → wcgw-1.3.0}/.github/workflows/python-publish.yml +0 -0
  12. {wcgw-1.2.2 → wcgw-1.3.0}/.github/workflows/python-tests.yml +0 -0
  13. {wcgw-1.2.2 → wcgw-1.3.0}/.gitignore +0 -0
  14. {wcgw-1.2.2 → wcgw-1.3.0}/.python-version +0 -0
  15. {wcgw-1.2.2 → wcgw-1.3.0}/.vscode/settings.json +0 -0
  16. {wcgw-1.2.2 → wcgw-1.3.0}/README.md +0 -0
  17. {wcgw-1.2.2 → wcgw-1.3.0}/src/__init__.py +0 -0
  18. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/__init__.py +0 -0
  19. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/__init__.py +0 -0
  20. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/__main__.py +0 -0
  21. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/cli.py +0 -0
  22. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/common.py +0 -0
  23. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/client/openai_utils.py +0 -0
  24. {wcgw-1.2.2 → wcgw-1.3.0}/src/wcgw/relay/static/privacy.txt +0 -0
  25. {wcgw-1.2.2 → wcgw-1.3.0}/static/ss1.png +0 -0
  26. {wcgw-1.2.2 → wcgw-1.3.0}/tests/test_basic.py +0 -0
  27. {wcgw-1.2.2 → wcgw-1.3.0}/tests/test_tools.py +0 -0
  28. {wcgw-1.2.2 → 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.2
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,6 +249,46 @@
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": {
@@ -320,8 +360,8 @@
320
360
  },
321
361
  "type": "object",
322
362
  "required": [
323
- "user_id",
324
- "type"
363
+ "type",
364
+ "user_id"
325
365
  ],
326
366
  "title": "BashInteractionWithUUID"
327
367
  },
@@ -405,6 +445,29 @@
405
445
  "type": "object",
406
446
  "title": "HTTPValidationError"
407
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
+ },
408
471
  "ReadFileWithUUID": {
409
472
  "properties": {
410
473
  "file_path": {
@@ -441,8 +504,7 @@
441
504
  true
442
505
  ],
443
506
  "const": true,
444
- "title": "Should Reset",
445
- "default": true
507
+ "title": "Should Reset"
446
508
  },
447
509
  "user_id": {
448
510
  "type": "string",
@@ -452,8 +514,8 @@
452
514
  },
453
515
  "type": "object",
454
516
  "required": [
455
- "user_id",
456
- "should_reset"
517
+ "should_reset",
518
+ "user_id"
457
519
  ],
458
520
  "title": "ResetShellWithUUID"
459
521
  },
@@ -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.
@@ -80,7 +83,7 @@ def call_hello_renamed():
80
83
  4. The lines to replace into the source code
81
84
  5. The end of the replace block: >>>>>>> REPLACE
82
85
 
83
- 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.
84
87
 
85
88
  *SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
86
89
  Including multiple unique *SEARCH/REPLACE* blocks if needed.
@@ -91,9 +94,12 @@ def call_hello_renamed():
91
94
  Include just the changing lines, and a few surrounding lines if needed for uniqueness.
92
95
  Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
93
96
 
97
+ Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.
98
+
94
99
  ---
95
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.
96
101
 
97
102
  Always write production ready, syntactically correct code.
98
103
  ---
99
- 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.2"
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,7 +220,7 @@ 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
 
@@ -227,10 +228,11 @@ Instructions:
227
228
  - First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
228
229
  - Always read relevant files before editing.
229
230
  - Do not provide code snippets unless asked by the user, instead directly edit the code.
230
-
231
+
231
232
  System information:
232
233
  - System: {uname_sysname}
233
234
  - Machine: {uname_machine}
235
+ - Current directory: {os.getcwd()}
234
236
  """
235
237
 
236
238
  with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f:
@@ -361,7 +363,7 @@ System information:
361
363
  enc,
362
364
  limit - cost,
363
365
  loop,
364
- max_tokens=8096,
366
+ max_tokens=8000,
365
367
  )
366
368
  except Exception as e:
367
369
  output_or_done = (
@@ -4,6 +4,7 @@ Instructions for editing files.
4
4
 
5
5
  Only edit the files using the following SEARCH/REPLACE blocks.
6
6
  ```
7
+ file_edit_using_search_replace_blocks="""
7
8
  <<<<<<< SEARCH
8
9
  def hello():
9
10
  "print a greeting"
@@ -32,6 +33,7 @@ def call_hello_renamed():
32
33
  hello_renamed()
33
34
  impl2()
34
35
  >>>>>>> REPLACE
36
+ """
35
37
  ```
36
38
 
37
39
  # *SEARCH/REPLACE block* Rules:
@@ -43,7 +45,7 @@ Every *SEARCH/REPLACE block* must use this format:
43
45
  4. The lines to replace into the source code
44
46
  5. The end of the replace block: >>>>>>> REPLACE
45
47
 
46
- 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.
47
49
 
48
50
  *SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
49
51
  Including multiple unique *SEARCH/REPLACE* blocks if needed.
@@ -53,3 +55,5 @@ Keep *SEARCH/REPLACE* blocks concise.
53
55
  Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
54
56
  Include just the changing lines, and a few surrounding lines if needed for uniqueness.
55
57
  Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
58
+
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,7 +223,7 @@ 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
 
@@ -235,6 +235,7 @@ Instructions:
235
235
  System information:
236
236
  - System: {uname_sysname}
237
237
  - Machine: {uname_machine}
238
+ - Current directory: {os.getcwd()}
238
239
 
239
240
  """
240
241
 
@@ -341,7 +342,7 @@ System information:
341
342
  enc,
342
343
  limit - cost,
343
344
  loop,
344
- max_tokens=2048,
345
+ max_tokens=8000,
345
346
  )
346
347
  except Exception as e:
347
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)
@@ -306,9 +317,7 @@ def execute_bash(
306
317
  updated_repl_mode = update_repl_prompt(bash_arg.send_text)
307
318
  if updated_repl_mode:
308
319
  BASH_STATE = "repl"
309
- response = (
310
- "Prompt updated, you can execute REPL lines using BashCommand now"
311
- )
320
+ response = "Prompt updated, you can execute REPL lines using BashCommand now"
312
321
  console.print(response)
313
322
  return (
314
323
  response,
@@ -334,7 +343,7 @@ def execute_bash(
334
343
  tokens = enc.encode(text)
335
344
 
336
345
  if max_tokens and len(tokens) >= max_tokens:
337
- text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
346
+ text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
338
347
 
339
348
  if is_interrupt:
340
349
  text = (
@@ -361,7 +370,7 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
361
370
 
362
371
  tokens = enc.encode(output)
363
372
  if max_tokens and len(tokens) >= max_tokens:
364
- output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
373
+ output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
365
374
 
366
375
  try:
367
376
  exit_status = get_status()
@@ -436,7 +445,7 @@ def read_image_from_shell(file_path: str) -> ImageData:
436
445
  image_bytes = image_file.read()
437
446
  image_b64 = base64.b64encode(image_bytes).decode("utf-8")
438
447
  image_type = mimetypes.guess_type(file_path)[0]
439
- return ImageData(media_type=image_type, data=image_b64)
448
+ return ImageData(media_type=image_type, data=image_b64) # type: ignore
440
449
 
441
450
 
442
451
  def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
@@ -488,15 +497,21 @@ def find_least_edit_distance_substring(
488
497
  edit_distance_sum = 0
489
498
  for j in range(len(find_lines)):
490
499
  if (i + j) < len(content_lines):
491
- edit_distance_sum += edit_distance(
492
- content_lines[i + j], find_lines[j])
500
+ edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
493
501
  else:
494
502
  edit_distance_sum += len(find_lines[j])
495
503
  if edit_distance_sum < min_edit_distance:
496
504
  min_edit_distance = edit_distance_sum
497
505
  orig_start_index = new_to_original_indices[i]
498
- orig_end_index = new_to_original_indices.get(i + len(find_lines) - 1, len(orig_content_lines) - 1) + 1
499
- 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
+ ]
500
515
  return "\n".join(min_edit_distance_lines), min_edit_distance
501
516
 
502
517
 
@@ -525,7 +540,8 @@ def do_diff_edit(fedit: FileEdit) -> str:
525
540
 
526
541
  if not os.path.isabs(fedit.file_path):
527
542
  raise Exception(
528
- 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
+ )
529
545
  else:
530
546
  path_ = fedit.file_path
531
547
 
@@ -535,13 +551,16 @@ def do_diff_edit(fedit: FileEdit) -> str:
535
551
  with open(path_) as f:
536
552
  apply_diff_to = f.read()
537
553
 
554
+ fedit.file_edit_using_search_replace_blocks = (
555
+ fedit.file_edit_using_search_replace_blocks.strip()
556
+ )
538
557
  lines = fedit.file_edit_using_search_replace_blocks.split("\n")
539
558
 
540
559
  if not lines or not re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[0]):
541
560
  raise Exception(
542
561
  "Error: first line should be `<<<<<< SEARCH` to start a search-replace block"
543
562
  )
544
-
563
+
545
564
  n_lines = len(lines)
546
565
  i = 0
547
566
  replacement_count = 0
@@ -561,15 +580,14 @@ def do_diff_edit(fedit: FileEdit) -> str:
561
580
 
562
581
  for line in search_block:
563
582
  console.log("> " + line)
564
- console.log("---")
583
+ console.log("=======")
565
584
  for line in replace_block:
566
585
  console.log("< " + line)
567
-
586
+ console.log("\n\n\n\n")
568
587
  search_block_ = "\n".join(search_block)
569
588
  replace_block_ = "\n".join(replace_block)
570
589
 
571
- apply_diff_to = edit_content(
572
- apply_diff_to, search_block_, replace_block_)
590
+ apply_diff_to = edit_content(apply_diff_to, search_block_, replace_block_)
573
591
  replacement_count += 1
574
592
  else:
575
593
  i += 1
@@ -588,7 +606,8 @@ def do_diff_edit(fedit: FileEdit) -> str:
588
606
  def file_edit(fedit: FileEditFindReplace) -> str:
589
607
  if not os.path.isabs(fedit.file_path):
590
608
  raise Exception(
591
- 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
+ )
592
611
  else:
593
612
  path_ = fedit.file_path
594
613
 
@@ -598,17 +617,14 @@ def file_edit(fedit: FileEditFindReplace) -> str:
598
617
  if not fedit.find_lines:
599
618
  raise Exception("Error: `find_lines` cannot be empty")
600
619
 
601
- out_string = "\n".join(
602
- "> " + line for line in fedit.find_lines.split("\n"))
603
- in_string = "\n".join(
604
- "< " + 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"))
605
622
  console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
606
623
  try:
607
624
  with open(path_) as f:
608
625
  content = f.read()
609
626
 
610
- content = edit_content(content, fedit.find_lines,
611
- fedit.replace_with_lines)
627
+ content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
612
628
 
613
629
  with open(path_, "w") as f:
614
630
  f.write(content)
@@ -653,6 +669,7 @@ TOOLS = (
653
669
  | DoneFlag
654
670
  | ReadImage
655
671
  | ReadFile
672
+ | Initialize
656
673
  )
657
674
 
658
675
 
@@ -686,6 +703,8 @@ def which_tool_name(name: str) -> Type[TOOLS]:
686
703
  return ReadImage
687
704
  elif name == "ReadFile":
688
705
  return ReadFile
706
+ elif name == "Initialize":
707
+ return Initialize
689
708
  else:
690
709
  raise ValueError(f"Unknown tool name: {name}")
691
710
 
@@ -703,6 +722,7 @@ def get_tool_output(
703
722
  | AIAssistant
704
723
  | DoneFlag
705
724
  | ReadImage
725
+ | Initialize
706
726
  | ReadFile,
707
727
  enc: tiktoken.Encoding,
708
728
  limit: float,
@@ -723,6 +743,7 @@ def get_tool_output(
723
743
  | DoneFlag
724
744
  | ReadImage
725
745
  | ReadFile
746
+ | Initialize
726
747
  ](
727
748
  Confirmation
728
749
  | BashCommand
@@ -736,6 +757,7 @@ def get_tool_output(
736
757
  | DoneFlag
737
758
  | ReadImage
738
759
  | ReadFile
760
+ | Initialize
739
761
  )
740
762
  arg = adapter.validate_python(args)
741
763
  else:
@@ -770,10 +792,13 @@ def get_tool_output(
770
792
  output = read_image_from_shell(arg.file_path), 0.0
771
793
  elif isinstance(arg, ReadFile):
772
794
  console.print("Calling read file tool")
773
- output = read_file(arg), 0.0
795
+ output = read_file(arg, max_tokens), 0.0
774
796
  elif isinstance(arg, ResetShell):
775
797
  console.print("Calling reset shell tool")
776
798
  output = reset_shell(), 0.0
799
+ elif isinstance(arg, Initialize):
800
+ console.print("Calling initial info tool")
801
+ output = initial_info(), 0.0
777
802
  else:
778
803
  raise ValueError(f"Unknown tool: {arg}")
779
804
 
@@ -785,8 +810,7 @@ History = list[ChatCompletionMessageParam]
785
810
 
786
811
  default_enc = tiktoken.encoding_for_model("gpt-4o")
787
812
  default_model: Models = "gpt-4o-2024-08-06"
788
- default_cost = CostData(cost_per_1m_input_tokens=0.15,
789
- 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)
790
814
  curr_cost = 0.0
791
815
 
792
816
 
@@ -801,6 +825,7 @@ class Mdata(BaseModel):
801
825
  | FileEdit
802
826
  | str
803
827
  | ReadFile
828
+ | Initialize
804
829
  )
805
830
 
806
831
 
@@ -829,8 +854,7 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
829
854
  raise Exception(mdata)
830
855
  try:
831
856
  output, cost = get_tool_output(
832
- mdata.data, default_enc, 0.0, lambda x, y: (
833
- "", 0), None
857
+ mdata.data, default_enc, 0.0, lambda x, y: ("", 0), 8000
834
858
  )
835
859
  curr_cost += cost
836
860
  print(f"{curr_cost=}")
@@ -863,8 +887,7 @@ def app(
863
887
  register_client(server_url, client_uuid or "")
864
888
 
865
889
 
866
- def read_file(readfile: ReadFile) -> str:
867
-
890
+ def read_file(readfile: ReadFile, max_tokens: Optional[int]) -> str:
868
891
  console.print(f"Reading file: {readfile.file_path}")
869
892
 
870
893
  if not os.path.isabs(readfile.file_path):
@@ -876,4 +899,11 @@ def read_file(readfile: ReadFile) -> str:
876
899
 
877
900
  with path.open("r") as f:
878
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
+
879
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()
@@ -52,3 +52,7 @@ class ResetShell(BaseModel):
52
52
  class FileEdit(BaseModel):
53
53
  file_path: str
54
54
  file_edit_using_search_replace_blocks: str
55
+
56
+
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