diff --git a/gpt_action_json_schema.json b/gpt_action_json_schema.json index 4fb9761..c264628 100644 --- a/gpt_action_json_schema.json +++ b/gpt_action_json_schema.json @@ -19,7 +19,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateFileNewWithUUID" + "$ref": "#/components/schemas/WriteIfEmptyWithUUID" } } }, @@ -384,7 +384,7 @@ ], "title": "CommandWithUUID" }, - "CreateFileNewWithUUID": { + "WriteIfEmptyWithUUID": { "properties": { "file_path": { "type": "string", @@ -406,7 +406,7 @@ "file_content", "user_id" ], - "title": "CreateFileNewWithUUID" + "title": "WriteIfEmptyWithUUID" }, "FileEditWithUUID": { "properties": { diff --git a/gpt_instructions.txt b/gpt_instructions.txt index f4467e3..36cfd96 100644 --- a/gpt_instructions.txt +++ b/gpt_instructions.txt @@ -24,8 +24,8 @@ Instructions for `Read File` - Read full content of a file. - Provide absolute file path only. -Instructions for `Create File New` -- Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files. +Instructions for `Write if Empty` +- Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files. - Provide absolute file path only. - For editing existing files, use FileEdit. diff --git a/pyproject.toml b/pyproject.toml index 40050fd..c4aba9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,12 @@ [project] authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }] name = "wcgw" -version = "2.1.2" +version = "2.2.0" description = "Shell and coding agent on claude and chatgpt" readme = "README.md" requires-python = ">=3.11, <3.13" dependencies = [ "openai>=1.46.0", - "mypy>=1.11.2", "typer>=0.12.5", "rich>=13.8.1", "python-dotenv>=1.0.1", diff --git a/src/wcgw/client/anthropic_client.py b/src/wcgw/client/anthropic_client.py index 97d139a..544eeb6 100644 --- a/src/wcgw/client/anthropic_client.py +++ b/src/wcgw/client/anthropic_client.py @@ -24,7 +24,7 @@ from ..types_ import ( BashCommand, BashInteraction, - CreateFileNew, + WriteIfEmpty, FileEditFindReplace, FileEdit, Keyboard, @@ -198,10 +198,10 @@ def loop( """, ), ToolParam( - input_schema=CreateFileNew.model_json_schema(), - name="CreateFileNew", + input_schema=WriteIfEmpty.model_json_schema(), + name="WriteIfEmpty", description=""" -- Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files. +- Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files. - Provide absolute file path only. - For editing existing files, use FileEdit instead of this tool. """, @@ -495,7 +495,12 @@ def loop( ) else: _histories.append( - {"role": "assistant", "content": full_response} + { + "role": "assistant", + "content": full_response + if full_response.strip() + else "...", + } # Fixes anthropic issue of non empty response only ) except KeyboardInterrupt: diff --git a/src/wcgw/client/mcp_server/server.py b/src/wcgw/client/mcp_server/server.py index 74f9aca..eaf4284 100644 --- a/src/wcgw/client/mcp_server/server.py +++ b/src/wcgw/client/mcp_server/server.py @@ -17,7 +17,7 @@ from ...types_ import ( BashCommand, BashInteraction, - CreateFileNew, + WriteIfEmpty, FileEdit, Keyboard, Mouse, @@ -117,10 +117,10 @@ async def handle_list_tools() -> list[types.Tool]: """, ), ToolParam( - inputSchema=CreateFileNew.model_json_schema(), - name="CreateFileNew", + inputSchema=WriteIfEmpty.model_json_schema(), + name="WriteIfEmpty", description=""" -- Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files. +- Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files. - Provide absolute file path only. - For editing existing files, use FileEdit instead of this tool. """, diff --git a/src/wcgw/client/openai_client.py b/src/wcgw/client/openai_client.py index 17227cb..9603a0e 100644 --- a/src/wcgw/client/openai_client.py +++ b/src/wcgw/client/openai_client.py @@ -23,7 +23,7 @@ from ..types_ import ( BashCommand, BashInteraction, - CreateFileNew, + WriteIfEmpty, FileEdit, ReadImage, ReadFile, @@ -195,9 +195,9 @@ def loop( """, ), openai.pydantic_function_tool( - CreateFileNew, + WriteIfEmpty, description=""" -- Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files. +- Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files. - Provide absolute file path only. - For editing existing files, use FileEdit instead of this tool.""", ), diff --git a/src/wcgw/client/tools.py b/src/wcgw/client/tools.py index 8c04cc3..28304ad 100644 --- a/src/wcgw/client/tools.py +++ b/src/wcgw/client/tools.py @@ -55,7 +55,7 @@ from ..types_ import ( BashCommand, BashInteraction, - CreateFileNew, + WriteIfEmpty, FileEditFindReplace, FileEdit, Initialize, @@ -531,7 +531,7 @@ def read_image_from_shell(file_path: str) -> ImageData: return ImageData(media_type=image_type, data=image_b64) # type: ignore -def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str: +def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str: if not os.path.isabs(writefile.file_path): return f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}" else: @@ -620,7 +620,7 @@ def find_least_edit_distance_substring( + 1 ) min_edit_distance_lines = orig_content_lines[ - orig_start_index:orig_end_index + max(0, orig_start_index - 10) : (orig_end_index + 10) ] return "\n".join(min_edit_distance_lines), min_edit_distance @@ -638,7 +638,9 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str: f"Exact match not found, found with whitespace removed edit distance: {min_edit_distance}" ) raise Exception( - f"Error: no match found for the provided `find_lines` in the file. Closest match:\n---\n{closest_match}\n---\nFile not edited" + f"""Error: no match found for the provided search block. + Requested search block: \n```\n{find_lines}\n``` + Possible relevant section in the file:\n---\n```\n{closest_match}\n```\n---\nFile not edited""" ) content = content.replace(find_lines, replace_with_lines, 1) @@ -765,7 +767,7 @@ def take_help_of_ai_assistant( | BashCommand | BashInteraction | ResetShell - | CreateFileNew + | WriteIfEmpty | FileEditFindReplace | FileEdit | AIAssistant @@ -794,8 +796,8 @@ def which_tool_name(name: str) -> Type[TOOLS]: return BashInteraction elif name == "ResetShell": return ResetShell - elif name == "CreateFileNew": - return CreateFileNew + elif name == "WriteIfEmpty": + return WriteIfEmpty elif name == "FileEditFindReplace": return FileEditFindReplace elif name == "FileEdit": @@ -828,7 +830,7 @@ def get_tool_output( | BashCommand | BashInteraction | ResetShell - | CreateFileNew + | WriteIfEmpty | FileEditFindReplace | FileEdit | AIAssistant @@ -852,7 +854,7 @@ def get_tool_output( | BashCommand | BashInteraction | ResetShell - | CreateFileNew + | WriteIfEmpty | FileEditFindReplace | FileEdit | AIAssistant @@ -869,7 +871,7 @@ def get_tool_output( | BashCommand | BashInteraction | ResetShell - | CreateFileNew + | WriteIfEmpty | FileEditFindReplace | FileEdit | AIAssistant @@ -892,7 +894,7 @@ def get_tool_output( elif isinstance(arg, (BashCommand | BashInteraction)): console.print("Calling execute bash tool") output = execute_bash(enc, arg, max_tokens, None) - elif isinstance(arg, CreateFileNew): + elif isinstance(arg, WriteIfEmpty): console.print("Calling write file tool") output = write_file(arg, True), 0 elif isinstance(arg, FileEdit): @@ -915,6 +917,8 @@ def get_tool_output( output = reset_shell(), 0.0 elif isinstance(arg, Initialize): console.print("Calling initial info tool") + # First force reset + reset_shell() output = initial_info(), 0.0 elif isinstance(arg, (Mouse, Keyboard, ScreenShot, GetScreenInfo)): console.print(f"Calling {type(arg).__name__} tool") @@ -974,7 +978,7 @@ class Mdata(BaseModel): data: ( BashCommand | BashInteraction - | CreateFileNew + | WriteIfEmpty | ResetShell | FileEditFindReplace | FileEdit diff --git a/src/wcgw/relay/serve.py b/src/wcgw/relay/serve.py index dcd21ad..dadbc1d 100644 --- a/src/wcgw/relay/serve.py +++ b/src/wcgw/relay/serve.py @@ -17,7 +17,7 @@ from ..types_ import ( BashCommand, BashInteraction, - CreateFileNew, + WriteIfEmpty, FileEditFindReplace, FileEdit, Initialize, @@ -31,7 +31,7 @@ class Mdata(BaseModel): data: ( BashCommand | BashInteraction - | CreateFileNew + | WriteIfEmpty | ResetShell | FileEditFindReplace | FileEdit @@ -99,12 +99,12 @@ async def send_data_callback(data: Mdata) -> None: print(f"Client {uuid} disconnected") -class CreateFileNewWithUUID(CreateFileNew): +class WriteIfEmptyWithUUID(WriteIfEmpty): user_id: UUID @app.post("/v1/create_file") -async def create_file(write_file_data: CreateFileNewWithUUID) -> str: +async def create_file(write_file_data: WriteIfEmptyWithUUID) -> str: user_id = write_file_data.user_id if user_id not in clients: return "Failure: id not found, ask the user to check it." diff --git a/src/wcgw/types_.py b/src/wcgw/types_.py index 5cea43e..35701e1 100644 --- a/src/wcgw/types_.py +++ b/src/wcgw/types_.py @@ -24,7 +24,7 @@ class ReadImage(BaseModel): type: Literal["ReadImage"] -class CreateFileNew(BaseModel): +class WriteIfEmpty(BaseModel): file_path: str file_content: str diff --git a/tests/test_tools.py b/tests/test_tools.py index a9b8ff3..024508f 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import patch from src.wcgw.client.tools import render_terminal_output, ask_confirmation, Confirmation -from src.wcgw.types_ import CreateFileNew +from src.wcgw.types_ import WriteIfEmpty class TestTools(unittest.TestCase): @@ -32,7 +32,7 @@ def test_ask_confirmation_no(self, mock_input): def test_writefile_model(self): # Test the Writefile Pydantic model - file = CreateFileNew(file_path="test.txt", file_content="This is a test.") + file = WriteIfEmpty(file_path="test.txt", file_content="This is a test.") self.assertEqual(file.file_path, "test.txt") self.assertEqual(file.file_content, "This is a test.") diff --git a/uv.lock b/uv.lock index 359c1e4..eb03e2e 100644 --- a/uv.lock +++ b/uv.lock @@ -869,14 +869,13 @@ wheels = [ [[package]] name = "wcgw" -version = "2.1.2" +version = "2.2.0" source = { editable = "." } dependencies = [ { name = "anthropic" }, { name = "fastapi" }, { name = "humanize" }, { name = "mcp" }, - { name = "mypy" }, { name = "nltk" }, { name = "openai" }, { name = "petname" }, @@ -910,7 +909,6 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.115.0" }, { name = "humanize", specifier = ">=4.11.0" }, { name = "mcp", git = "https://github.com/rusiaaman/python-sdk?rev=53b69f397eae6ac81a51b84b34ff52b3119f11cb" }, - { name = "mypy", specifier = ">=1.11.2" }, { name = "nltk", specifier = ">=3.9.1" }, { name = "openai", specifier = ">=1.46.0" }, { name = "petname", specifier = ">=2.6" },