wcgw 2.8.7__tar.gz → 2.8.9__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 (170) hide show
  1. {wcgw-2.8.7 → wcgw-2.8.9}/PKG-INFO +1 -1
  2. {wcgw-2.8.7 → wcgw-2.8.9}/pyproject.toml +1 -1
  3. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/tools.py +15 -3
  4. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw_cli/anthropic_client.py +82 -10
  5. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_initialize.py +75 -0
  6. {wcgw-2.8.7 → wcgw-2.8.9}/.github/workflows/python-publish.yml +0 -0
  7. {wcgw-2.8.7 → wcgw-2.8.9}/.github/workflows/python-tests.yml +0 -0
  8. {wcgw-2.8.7 → wcgw-2.8.9}/.github/workflows/python-types.yml +0 -0
  9. {wcgw-2.8.7 → wcgw-2.8.9}/.gitignore +0 -0
  10. {wcgw-2.8.7 → wcgw-2.8.9}/.gitmodules +0 -0
  11. {wcgw-2.8.7 → wcgw-2.8.9}/.python-version +0 -0
  12. {wcgw-2.8.7 → wcgw-2.8.9}/.vscode/settings.json +0 -0
  13. {wcgw-2.8.7 → wcgw-2.8.9}/Dockerfile +0 -0
  14. {wcgw-2.8.7 → wcgw-2.8.9}/LICENSE +0 -0
  15. {wcgw-2.8.7 → wcgw-2.8.9}/README.md +0 -0
  16. {wcgw-2.8.7 → wcgw-2.8.9}/gpt_action_json_schema.json +0 -0
  17. {wcgw-2.8.7 → wcgw-2.8.9}/gpt_instructions.txt +0 -0
  18. {wcgw-2.8.7 → wcgw-2.8.9}/openai.md +0 -0
  19. {wcgw-2.8.7 → wcgw-2.8.9}/src/__init__.py +0 -0
  20. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.git +0 -0
  21. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  22. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  23. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.github/workflows/main-checks.yml +0 -0
  24. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.github/workflows/publish-pypi.yml +0 -0
  25. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.github/workflows/pull-request-checks.yml +0 -0
  26. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.github/workflows/shared.yml +0 -0
  27. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.gitignore +0 -0
  28. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/.python-version +0 -0
  29. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/CODE_OF_CONDUCT.md +0 -0
  30. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/CONTRIBUTING.md +0 -0
  31. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/LICENSE +0 -0
  32. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/README.md +0 -0
  33. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/RELEASE.md +0 -0
  34. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/SECURITY.md +0 -0
  35. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/README.md +0 -0
  36. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-prompt/.python-version +0 -0
  37. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-prompt/README.md +0 -0
  38. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
  39. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
  40. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
  41. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-prompt/pyproject.toml +0 -0
  42. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-resource/.python-version +0 -0
  43. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-resource/README.md +0 -0
  44. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
  45. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
  46. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
  47. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-resource/pyproject.toml +0 -0
  48. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-tool/.python-version +0 -0
  49. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-tool/README.md +0 -0
  50. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
  51. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
  52. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
  53. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/examples/servers/simple-tool/pyproject.toml +0 -0
  54. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/pyproject.toml +0 -0
  55. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/__init__.py +0 -0
  56. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__init__.py +0 -0
  57. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__main__.py +0 -0
  58. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/client/session.py +0 -0
  59. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/client/sse.py +0 -0
  60. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/client/stdio.py +0 -0
  61. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/py.typed +0 -0
  62. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__init__.py +0 -0
  63. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__main__.py +0 -0
  64. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/server/models.py +0 -0
  65. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/server/session.py +0 -0
  66. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/server/sse.py +0 -0
  67. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/server/stdio.py +0 -0
  68. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/server/websocket.py +0 -0
  69. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/__init__.py +0 -0
  70. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/context.py +0 -0
  71. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/exceptions.py +0 -0
  72. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/memory.py +0 -0
  73. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/progress.py +0 -0
  74. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/session.py +0 -0
  75. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/version.py +0 -0
  76. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/src/mcp_wcgw/types.py +0 -0
  77. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/__init__.py +0 -0
  78. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/client/__init__.py +0 -0
  79. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/client/test_session.py +0 -0
  80. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/client/test_stdio.py +0 -0
  81. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/conftest.py +0 -0
  82. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/server/__init__.py +0 -0
  83. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/server/test_session.py +0 -0
  84. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/server/test_stdio.py +0 -0
  85. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/shared/test_memory.py +0 -0
  86. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/tests/test_types.py +0 -0
  87. {wcgw-2.8.7 → wcgw-2.8.9}/src/mcp_wcgw_fork/uv.lock +0 -0
  88. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/__init__.py +0 -0
  89. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/__init__.py +0 -0
  90. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/common.py +0 -0
  91. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/computer_use.py +0 -0
  92. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/diff-instructions.txt +0 -0
  93. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/file_ops/diff_edit.py +0 -0
  94. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/file_ops/search_replace.py +0 -0
  95. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/mcp_server/Readme.md +0 -0
  96. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/mcp_server/__init__.py +0 -0
  97. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/mcp_server/server.py +0 -0
  98. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/memory.py +0 -0
  99. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/modes.py +0 -0
  100. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/repo_ops/display_tree.py +0 -0
  101. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/repo_ops/path_prob.py +0 -0
  102. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
  103. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
  104. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/repo_ops/repo_context.py +0 -0
  105. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/client/sys_utils.py +0 -0
  106. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/relay/serve.py +0 -0
  107. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/relay/static/privacy.txt +0 -0
  108. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw/types_.py +0 -0
  109. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw_cli/__init__.py +0 -0
  110. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw_cli/__main__.py +0 -0
  111. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw_cli/cli.py +0 -0
  112. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw_cli/openai_client.py +0 -0
  113. {wcgw-2.8.7 → wcgw-2.8.9}/src/wcgw_cli/openai_utils.py +0 -0
  114. {wcgw-2.8.7 → wcgw-2.8.9}/static/claude-ss.jpg +0 -0
  115. {wcgw-2.8.7 → wcgw-2.8.9}/static/computer-use.jpg +0 -0
  116. {wcgw-2.8.7 → wcgw-2.8.9}/static/example.jpg +0 -0
  117. {wcgw-2.8.7 → wcgw-2.8.9}/static/rocket-icon.png +0 -0
  118. {wcgw-2.8.7 → wcgw-2.8.9}/static/ss1.png +0 -0
  119. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/file_ops/test_diff_edit.py +0 -0
  120. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/file_ops/test_search_replace.py +0 -0
  121. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/repo_ops/__init__.py +0 -0
  122. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/repo_ops/test_display_tree.py +0 -0
  123. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/repo_ops/test_display_tree_simple.py +0 -0
  124. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/repo_ops/test_path_prob.py +0 -0
  125. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/repo_ops/test_repo_context.py +0 -0
  126. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_anthropic_client_utils.py +0 -0
  127. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_memory.py +0 -0
  128. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_openai_utils.py +0 -0
  129. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_tools_basic.py +0 -0
  130. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_tools_extended.py +0 -0
  131. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_tools_file_ops.py +0 -0
  132. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_tools_files.py +0 -0
  133. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_tools_shell.py +0 -0
  134. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/test_tools_validation.py +0 -0
  135. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/__init__.py +0 -0
  136. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_command_validation.py +0 -0
  137. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_docker_operations.py +0 -0
  138. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_error_handling.py +0 -0
  139. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_error_handling_full.py +0 -0
  140. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_execute_bash.py +0 -0
  141. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_file_errors.py +0 -0
  142. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_file_operations.py +0 -0
  143. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_files/test1.py +0 -0
  144. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_files/test2.py +0 -0
  145. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_files/test_file.py +0 -0
  146. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_full_coverage.py +0 -0
  147. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_is_int.py +0 -0
  148. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_knowledge_transfer.py +0 -0
  149. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_large_blocks.py +0 -0
  150. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_prompt_update.py +0 -0
  151. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_render_terminal.py +0 -0
  152. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_terminal_output.py +0 -0
  153. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_terminal_output_full.py +0 -0
  154. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_terminal_output_incremental.py +0 -0
  155. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_terminal_output_raw.py +0 -0
  156. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_terminal_sequence.py +0 -0
  157. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_timeout_handling.py +0 -0
  158. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_timeout_state.py +0 -0
  159. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_user_interaction.py +0 -0
  160. {wcgw-2.8.7 → wcgw-2.8.9}/tests/client/tools/test_write_file.py +0 -0
  161. {wcgw-2.8.7 → wcgw-2.8.9}/tests/conftest.py +0 -0
  162. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_anthropic_client.py +0 -0
  163. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_basic.py +0 -0
  164. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_common.py +0 -0
  165. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_computer_use.py +0 -0
  166. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_computer_use_base.py +0 -0
  167. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_computer_use_shell.py +0 -0
  168. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_sys_utils.py +0 -0
  169. {wcgw-2.8.7 → wcgw-2.8.9}/tests/test_tools.py +0 -0
  170. {wcgw-2.8.7 → wcgw-2.8.9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 2.8.7
3
+ Version: 2.8.9
4
4
  Summary: Shell and coding agent on claude and chatgpt
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
3
3
  name = "wcgw"
4
- version = "2.8.7"
4
+ version = "2.8.9"
5
5
  description = "Shell and coding agent on claude and chatgpt"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.11, <3.13"
@@ -146,6 +146,7 @@ def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: #
146
146
  encoding="utf-8",
147
147
  timeout=TIMEOUT,
148
148
  cwd=initial_dir,
149
+ codec_errors="backslashreplace",
149
150
  )
150
151
  shell.sendline(
151
152
  f"export PROMPT_COMMAND= PS1={PROMPT_CONST}"
@@ -161,6 +162,7 @@ def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: #
161
162
  echo=False,
162
163
  encoding="utf-8",
163
164
  timeout=TIMEOUT,
165
+ codec_errors="backslashreplace",
164
166
  )
165
167
  shell.sendline(f"export PS1={PROMPT_CONST}")
166
168
  shell.expect(PROMPT_CONST, timeout=TIMEOUT)
@@ -256,7 +258,9 @@ class BashState:
256
258
  before = "\n".join(before_lines).strip()
257
259
  counts += 1
258
260
  if counts > 100:
259
- raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
261
+ raise ValueError(
262
+ "Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it"
263
+ )
260
264
 
261
265
  try:
262
266
  return int(before)
@@ -273,7 +277,7 @@ class BashState:
273
277
  self._bash_command_mode.bash_mode == "restricted_mode",
274
278
  self._cwd,
275
279
  )
276
-
280
+
277
281
  self._pending_output = ""
278
282
 
279
283
  # Get exit info to ensure shell is ready
@@ -414,7 +418,9 @@ class BashState:
414
418
  index = self.shell.expect([self._prompt, pexpect.TIMEOUT], timeout=0.2)
415
419
  counts += 1
416
420
  if counts > 100:
417
- raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
421
+ raise ValueError(
422
+ "Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it"
423
+ )
418
424
  console.print(f"Prompt updated to: {self._prompt}")
419
425
  return True
420
426
  return False
@@ -457,6 +463,12 @@ def initialize(
457
463
  folder_to_start = None
458
464
  if any_workspace_path:
459
465
  if os.path.exists(any_workspace_path):
466
+ if os.path.isfile(any_workspace_path):
467
+ # Set any_workspace_path to the directory containing the file
468
+ # Add the file to read_files_ only if empty to avoid duplicates
469
+ if not read_files_:
470
+ read_files_ = [any_workspace_path]
471
+ any_workspace_path = os.path.dirname(any_workspace_path)
460
472
  repo_context, folder_to_start = get_repo_context(any_workspace_path, 200)
461
473
 
462
474
  repo_context = f"---\n# Workspace structure\n{repo_context}\n---\n"
@@ -14,15 +14,17 @@ from anthropic import Anthropic
14
14
  from anthropic.types import (
15
15
  ImageBlockParam,
16
16
  MessageParam,
17
+ ModelParam,
17
18
  TextBlockParam,
18
19
  ToolParam,
19
20
  ToolResultBlockParam,
20
21
  ToolUseBlockParam,
21
22
  )
22
23
  from dotenv import load_dotenv
24
+ from pydantic import BaseModel
23
25
  from typer import Typer
24
26
 
25
- from wcgw.client.common import discard_input
27
+ from wcgw.client.common import CostData, discard_input
26
28
  from wcgw.client.memory import load_memory
27
29
  from wcgw.client.tools import (
28
30
  DoneFlag,
@@ -47,6 +49,14 @@ from wcgw.types_ import (
47
49
  WriteIfEmpty,
48
50
  )
49
51
 
52
+
53
+ class Config(BaseModel):
54
+ model: ModelParam
55
+ cost_limit: float
56
+ cost_file: dict[ModelParam, CostData]
57
+ cost_unit: str = "$"
58
+
59
+
50
60
  History = list[MessageParam]
51
61
 
52
62
 
@@ -150,7 +160,51 @@ def loop(
150
160
  first_message = ""
151
161
  waiting_for_assistant = history[-1]["role"] != "assistant"
152
162
 
153
- limit = 1
163
+ config = Config(
164
+ model="claude-3-5-sonnet-20241022",
165
+ cost_limit=0.1,
166
+ cost_unit="$",
167
+ cost_file={
168
+ # Claude 3.5 Haiku
169
+ "claude-3-5-haiku-latest": CostData(
170
+ cost_per_1m_input_tokens=0.80, cost_per_1m_output_tokens=4
171
+ ),
172
+ "claude-3-5-haiku-20241022": CostData(
173
+ cost_per_1m_input_tokens=0.80, cost_per_1m_output_tokens=4
174
+ ),
175
+ # Claude 3.5 Sonnet
176
+ "claude-3-5-sonnet-latest": CostData(
177
+ cost_per_1m_input_tokens=3.0, cost_per_1m_output_tokens=15.0
178
+ ),
179
+ "claude-3-5-sonnet-20241022": CostData(
180
+ cost_per_1m_input_tokens=3.0, cost_per_1m_output_tokens=15.0
181
+ ),
182
+ "claude-3-5-sonnet-20240620": CostData(
183
+ cost_per_1m_input_tokens=3.0, cost_per_1m_output_tokens=15.0
184
+ ),
185
+ # Claude 3 Opus
186
+ "claude-3-opus-latest": CostData(
187
+ cost_per_1m_input_tokens=15.0, cost_per_1m_output_tokens=75.0
188
+ ),
189
+ "claude-3-opus-20240229": CostData(
190
+ cost_per_1m_input_tokens=15.0, cost_per_1m_output_tokens=75.0
191
+ ),
192
+ # Legacy Models
193
+ "claude-3-haiku-20240307": CostData(
194
+ cost_per_1m_input_tokens=0.25, cost_per_1m_output_tokens=1.25
195
+ ),
196
+ "claude-2.1": CostData(
197
+ cost_per_1m_input_tokens=8.0, cost_per_1m_output_tokens=24.0
198
+ ),
199
+ "claude-2.0": CostData(
200
+ cost_per_1m_input_tokens=8.0, cost_per_1m_output_tokens=24.0
201
+ ),
202
+ },
203
+ )
204
+
205
+ if limit is not None:
206
+ config.cost_limit = limit
207
+ limit = config.cost_limit
154
208
 
155
209
  tools = [
156
210
  ToolParam(
@@ -321,9 +375,15 @@ Saves provided description and file contents of all the relevant file paths or g
321
375
  while True:
322
376
  if cost > limit:
323
377
  system_console.print(
324
- f"\nCost limit exceeded. Current cost: {cost}, input tokens: {input_toks}, output tokens: {output_toks}"
378
+ f"\nCost limit exceeded. Current cost: {config.cost_unit}{cost:.4f}, "
379
+ f"input tokens: {input_toks}"
380
+ f"output tokens: {output_toks}"
325
381
  )
326
382
  break
383
+ else:
384
+ system_console.print(
385
+ f"\nTotal cost: {config.cost_unit}{cost:.4f}, input tokens: {input_toks}, output tokens: {output_toks}"
386
+ )
327
387
 
328
388
  if not waiting_for_assistant:
329
389
  if first_message:
@@ -335,13 +395,8 @@ Saves provided description and file contents of all the relevant file paths or g
335
395
  history.append(parse_user_message_special(msg))
336
396
  else:
337
397
  waiting_for_assistant = False
338
-
339
- cost_, input_toks_ = 0, 0
340
- cost += cost_
341
- input_toks += input_toks_
342
-
343
398
  stream = client.messages.stream(
344
- model="claude-3-5-sonnet-20241022",
399
+ model=config.model,
345
400
  messages=history,
346
401
  tools=tools,
347
402
  max_tokens=8096,
@@ -361,7 +416,24 @@ Saves provided description and file contents of all the relevant file paths or g
361
416
  with stream as stream_:
362
417
  for chunk in stream_:
363
418
  type_ = chunk.type
364
- if type_ in {"message_start", "message_stop"}:
419
+ if type_ == "message_start":
420
+ message_start = chunk.message
421
+ # Update cost based on token usage from the API response
422
+ input_tokens = message_start.usage.input_tokens
423
+ input_toks += input_tokens
424
+ cost += (
425
+ input_tokens
426
+ * config.cost_file[config.model].cost_per_1m_input_tokens
427
+ ) / 1_000_000
428
+ elif type_ == "message_stop":
429
+ message_stop = chunk.message
430
+ # Update cost based on output tokens
431
+ output_tokens = message_stop.usage.output_tokens
432
+ output_toks += output_tokens
433
+ cost += (
434
+ output_tokens
435
+ * config.cost_file[config.model].cost_per_1m_output_tokens
436
+ ) / 1_000_000
365
437
  continue
366
438
  elif type_ == "content_block_start" and hasattr(
367
439
  chunk, "content_block"
@@ -248,3 +248,78 @@ class TestInitialize(unittest.TestCase):
248
248
 
249
249
  # Verify task memory was still loaded despite state error
250
250
  self.assertIn("Following is the retrieved task:\ntest_memory", result)
251
+
252
+
253
+ def test_workspace_path_is_file(self):
254
+ """Test initialize when workspace path points to a file"""
255
+ # Create a test file
256
+ test_file = os.path.join(self.test_workspace, "test.py")
257
+ os.makedirs(os.path.dirname(test_file), exist_ok=True)
258
+ with open(test_file, "w") as f:
259
+ f.write("print('test')")
260
+
261
+ with (
262
+ patch("os.path.exists") as mock_exists,
263
+ patch("os.path.isfile") as mock_isfile,
264
+ patch("wcgw.client.tools.get_repo_context") as mock_get_context,
265
+ patch("wcgw.client.tools.read_files") as mock_read_files,
266
+ ):
267
+ mock_exists.return_value = True
268
+ mock_isfile.return_value = True
269
+ mock_get_context.return_value = (self.repo_context, os.path.dirname(test_file))
270
+ mock_read_files.return_value = f"``` {test_file}print('test')```"
271
+
272
+ # Call initialize with file path and no read_files_
273
+ result = initialize(
274
+ any_workspace_path=test_file,
275
+ read_files_=[],
276
+ task_id_to_resume="",
277
+ max_tokens=None,
278
+ mode=Modes.wcgw,
279
+ )
280
+
281
+ # Verify read_files was called with the file path
282
+ mock_read_files.assert_called_once_with([test_file], None)
283
+
284
+ # Verify repo context uses parent directory
285
+ mock_get_context.assert_called_once_with(os.path.dirname(test_file), 200)
286
+
287
+ # Verify file content is in output
288
+ self.assertIn(f"``` {test_file}print('test')", result)
289
+
290
+ # Verify cwd
291
+ self.assertIn(f"Initialized in directory (also cwd): {os.path.dirname(test_file)}\n", result)
292
+
293
+ def test_workspace_path_is_file_with_read_files(self):
294
+ """Test initialize when workspace path points to a file and read_files_ is provided"""
295
+ test_file = os.path.join(self.test_workspace, "test.py")
296
+ extra_file = os.path.join(self.test_workspace, "extra.py")
297
+
298
+ with (
299
+ patch("os.path.exists") as mock_exists,
300
+ patch("os.path.isfile") as mock_isfile,
301
+ patch("wcgw.client.tools.get_repo_context") as mock_get_context,
302
+ patch("wcgw.client.tools.read_files") as mock_read_files,
303
+ ):
304
+ mock_exists.return_value = True
305
+ mock_isfile.return_value = True
306
+ mock_get_context.return_value = (self.repo_context, os.path.dirname(test_file))
307
+ mock_read_files.return_value = f"``` {extra_file}extra content```"
308
+
309
+ # Call initialize with file path and explicit read_files_
310
+ result = initialize(
311
+ any_workspace_path=test_file,
312
+ read_files_=[extra_file],
313
+ task_id_to_resume="",
314
+ max_tokens=None,
315
+ mode=Modes.wcgw,
316
+ )
317
+
318
+ # Verify read_files was called with the provided read_files_ only
319
+ mock_read_files.assert_called_once_with([extra_file], None)
320
+
321
+ # Verify repo context uses parent directory
322
+ mock_get_context.assert_called_once_with(os.path.dirname(test_file), 200)
323
+
324
+ # Verify extra file content is in output
325
+ self.assertIn(f"``` {extra_file}extra content", result)
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
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
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
File without changes
File without changes