"""Tests for the ReAct parsing module.""" import json from loader.runtime.parsing import format_tool_result, parse_tool_calls class TestParseToolCalls: """Tests for parse_tool_calls function.""" def test_parse_tool_call_xml_style(self): text = '''I need to read the file. {"name": "read", "arguments": {"file_path": "/tmp/test.txt"}} ''' result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "read" assert result.tool_calls[0].arguments == {"file_path": "/tmp/test.txt"} assert not result.is_final_answer def test_parse_multiple_tool_calls(self): text = ''' {"name": "read", "arguments": {"file_path": "a.txt"}} {"name": "read", "arguments": {"file_path": "b.txt"}} ''' result = parse_tool_calls(text) assert len(result.tool_calls) == 2 assert result.tool_calls[0].arguments["file_path"] == "a.txt" assert result.tool_calls[1].arguments["file_path"] == "b.txt" def test_parse_final_answer(self): text = '''Thought: I have all the information needed. Final Answer: The file contains a hello world program.''' result = parse_tool_calls(text) assert result.is_final_answer assert len(result.tool_calls) == 0 assert "hello world" in result.content.lower() def test_parse_no_tool_calls(self): text = "Just some regular text without any tool calls." result = parse_tool_calls(text) assert len(result.tool_calls) == 0 assert not result.is_final_answer assert "regular text" in result.content def test_parse_bare_json(self): text = '''Let me read that file. {"name": "read", "arguments": {"file_path": "/test.txt"}}''' result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "read" def test_parse_bare_json_todowrite_with_nested_items(self): text = json.dumps( { "name": "TodoWrite", "arguments": { "todos": [ { "content": "Run tests", "active_form": "Running tests", "status": "in_progress", } ] }, } ) result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "TodoWrite" assert result.tool_calls[0].arguments["todos"][0]["content"] == "Run tests" def test_parse_bare_json_patch_with_nested_hunks(self): text = json.dumps( { "name": "patch", "arguments": { "file_path": "sample.txt", "hunks": [ { "old_start": 2, "old_lines": 1, "new_start": 2, "new_lines": 1, "lines": ["-beta", "+beta updated"], } ], }, } ) result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "patch" assert result.tool_calls[0].arguments["hunks"][0]["lines"] == [ "-beta", "+beta updated", ] def test_parse_bare_json_ask_user_question_with_option_objects(self): text = json.dumps( { "name": "AskUserQuestion", "arguments": { "question": "Which path should we take?", "options": [ { "label": "Plan first", "description": "Write the plan before changing code.", }, { "label": "Execute now", "description": "Start implementing immediately.", }, ], }, } ) result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "AskUserQuestion" assert result.tool_calls[0].arguments["options"][1]["label"] == "Execute now" def test_parse_removes_react_labels(self): text = '''Thought: I need to check this. Action: {"name": "read", "arguments": {"file_path": "test.txt"}} ''' result = parse_tool_calls(text) assert "Thought:" not in result.content assert "Action:" not in result.content def test_parse_invalid_json_ignored(self): text = ''' {invalid json here} ''' result = parse_tool_calls(text) assert len(result.tool_calls) == 0 def test_parse_empty_arguments(self): text = ''' {"name": "pwd", "arguments": {}} ''' result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].arguments == {} def test_parse_parameters_alias(self): """Test that 'parameters' is accepted as alias for 'arguments'.""" text = ''' {"name": "read", "parameters": {"file_path": "/tmp/test.txt"}} ''' result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "read" assert result.tool_calls[0].arguments == {"file_path": "/tmp/test.txt"} def test_parse_malformed_closing_tag(self): """Test handling of malformed at start.""" text = ''' {"name": "read", "parameters": {"file_path": "test.txt"}} ''' result = parse_tool_calls(text) # Should clean up the malformed tags assert "" not in result.content def test_parse_cleans_orphaned_tags(self): """Test that orphaned tool_call tags are removed from content.""" text = '''Some text more text end''' result = parse_tool_calls(text) assert "" not in result.content assert "" not in result.content def test_parse_bracketed_calls_format(self): """Test parsing [calls tool with: key=value] format.""" text = '''I'll create the file now. [calls write tool with: file_path=/tmp/test.txt, content="hello world"] Created the file.''' result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "write" assert result.tool_calls[0].arguments["file_path"] == "/tmp/test.txt" assert result.tool_calls[0].arguments["content"] == "hello world" # Bracketed call should be removed from content assert "[calls" not in result.content def test_parse_bracketed_use_format(self): """Test parsing [USE tool: key=value] format.""" text = '[USE bash tool: command="ls -la"]' result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "bash" assert result.tool_calls[0].arguments["command"] == "ls -la" def test_parse_bracketed_edit_format(self): """Test parsing bracketed format with edit tool.""" text = '[calls edit tool with: file_path="test.py", old_string="foo", new_string="bar"]' result = parse_tool_calls(text) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "edit" assert result.tool_calls[0].arguments["file_path"] == "test.py" assert result.tool_calls[0].arguments["old_string"] == "foo" assert result.tool_calls[0].arguments["new_string"] == "bar" def test_parse_bracketed_mixed_case_tool_uses_allowed_name(self): text = '[calls askuserquestion tool with: question="Which path should we take?"]' result = parse_tool_calls( text, allowed_tool_names=["AskUserQuestion", "TodoWrite", "read"], ) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "AskUserQuestion" assert result.tool_calls[0].arguments == { "question": "Which path should we take?" } def test_parse_bare_json_filters_unknown_tool_when_allowed_names_provided(self): text = '{"name": "TotallyUnknownTool", "arguments": {"question": "ignored"}}' result = parse_tool_calls( text, allowed_tool_names=["AskUserQuestion", "TodoWrite", "read"], ) assert result.tool_calls == [] assert "TotallyUnknownTool" in result.content def test_parse_bare_json_maps_read_file_alias_to_read(self): text = '{"name": "read_file", "arguments": {"file_path": "/tmp/test.txt"}}' result = parse_tool_calls( text, allowed_tool_names=["read", "write", "patch"], ) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "read" assert result.tool_calls[0].arguments == {"file_path": "/tmp/test.txt"} def test_parse_fenced_read_command_into_tool_call(self): text = "Let me inspect the file first.\n```bash\nread /tmp/test.txt\n```" result = parse_tool_calls( text, allowed_tool_names=["read", "glob", "bash"], ) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "read" assert result.tool_calls[0].arguments == {"file_path": "/tmp/test.txt"} def test_parse_fenced_glob_command_into_tool_call(self): text = "```bash\nglob /tmp/guide/chapters/*.html\n```" result = parse_tool_calls( text, allowed_tool_names=["read", "glob", "bash"], ) assert len(result.tool_calls) == 1 assert result.tool_calls[0].name == "glob" assert result.tool_calls[0].arguments == { "pattern": "/tmp/guide/chapters/*.html" } class TestFormatToolResult: """Tests for format_tool_result function.""" def test_format_success(self): result = format_tool_result("read", "file contents here") assert "Observation" in result assert "read" in result assert "Result" in result assert "file contents here" in result def test_format_error(self): result = format_tool_result("write", "Permission denied", is_error=True) assert "Observation" in result assert "write" in result assert "Error" in result assert "Permission denied" in result def test_format_todowrite_compacts_payload(self): result = format_tool_result( "TodoWrite", json.dumps( { "old_todos": [ { "content": "Create index.html", "active_form": "Creating index.html", "status": "completed", } ], "new_todos": [ { "content": "Create index.html", "active_form": "Creating index.html", "status": "completed", }, { "content": "Create installation chapter (02-installation.html)", "active_form": "Creating installation chapter", "status": "pending", }, ], "verification_nudge_needed": False, "store_path": "/tmp/.loader/todos/active.json", } ), ) assert "Observation [TodoWrite]: Result: updated todo list" in result assert "1 completed" in result assert "1 pending" in result assert "next pending: Create installation chapter (02-installation.html)" in result assert "old_todos" not in result assert "new_todos" not in result