2025-11-04 05:17:27 +01:00
|
|
|
import time
|
|
|
|
|
import json
|
|
|
|
|
import logging
|
|
|
|
|
from pr.ui import Colors, display_tool_call, print_autonomous_header
|
|
|
|
|
from pr.autonomous.detection import is_task_complete
|
|
|
|
|
from pr.core.context import truncate_tool_result
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger('pr')
|
|
|
|
|
|
|
|
|
|
def run_autonomous_mode(assistant, task):
|
|
|
|
|
assistant.autonomous_mode = True
|
|
|
|
|
assistant.autonomous_iterations = 0
|
|
|
|
|
|
|
|
|
|
logger.debug(f"=== AUTONOMOUS MODE START ===")
|
|
|
|
|
logger.debug(f"Task: {task}")
|
|
|
|
|
|
|
|
|
|
assistant.messages.append({
|
|
|
|
|
"role": "user",
|
2025-11-04 05:57:23 +01:00
|
|
|
"content": f"{task}"
|
2025-11-04 05:17:27 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
while True:
|
|
|
|
|
assistant.autonomous_iterations += 1
|
|
|
|
|
|
|
|
|
|
logger.debug(f"--- Autonomous iteration {assistant.autonomous_iterations} ---")
|
|
|
|
|
logger.debug(f"Messages before context management: {len(assistant.messages)}")
|
|
|
|
|
|
|
|
|
|
from pr.core.context import manage_context_window
|
|
|
|
|
assistant.messages = manage_context_window(assistant.messages, assistant.verbose)
|
|
|
|
|
|
|
|
|
|
logger.debug(f"Messages after context management: {len(assistant.messages)}")
|
|
|
|
|
|
|
|
|
|
from pr.core.api import call_api
|
|
|
|
|
from pr.tools.base import get_tools_definition
|
|
|
|
|
response = call_api(
|
|
|
|
|
assistant.messages,
|
|
|
|
|
assistant.model,
|
|
|
|
|
assistant.api_url,
|
|
|
|
|
assistant.api_key,
|
|
|
|
|
assistant.use_tools,
|
|
|
|
|
get_tools_definition(),
|
|
|
|
|
verbose=assistant.verbose
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if 'error' in response:
|
|
|
|
|
logger.error(f"API error in autonomous mode: {response['error']}")
|
|
|
|
|
print(f"{Colors.RED}Error: {response['error']}{Colors.RESET}")
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
is_complete = is_task_complete(response, assistant.autonomous_iterations)
|
|
|
|
|
logger.debug(f"Task completion check: {is_complete}")
|
|
|
|
|
|
|
|
|
|
if is_complete:
|
|
|
|
|
result = process_response_autonomous(assistant, response)
|
|
|
|
|
print(f"\n{Colors.GREEN}r:{Colors.RESET} {result}\n")
|
|
|
|
|
|
|
|
|
|
logger.debug(f"=== AUTONOMOUS MODE COMPLETE ===")
|
|
|
|
|
logger.debug(f"Total iterations: {assistant.autonomous_iterations}")
|
|
|
|
|
logger.debug(f"Final message count: {len(assistant.messages)}")
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
result = process_response_autonomous(assistant, response)
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
print(f"\n{Colors.GREEN}r:{Colors.RESET} {result}\n")
|
|
|
|
|
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
logger.debug("Autonomous mode interrupted by user")
|
|
|
|
|
print(f"\n{Colors.YELLOW}Autonomous mode interrupted by user{Colors.RESET}")
|
|
|
|
|
finally:
|
|
|
|
|
assistant.autonomous_mode = False
|
|
|
|
|
logger.debug("=== AUTONOMOUS MODE END ===")
|
|
|
|
|
|
|
|
|
|
def process_response_autonomous(assistant, response):
|
|
|
|
|
if 'error' in response:
|
|
|
|
|
return f"Error: {response['error']}"
|
|
|
|
|
|
|
|
|
|
if 'choices' not in response or not response['choices']:
|
|
|
|
|
return "No response from API"
|
|
|
|
|
|
|
|
|
|
message = response['choices'][0]['message']
|
|
|
|
|
assistant.messages.append(message)
|
|
|
|
|
|
|
|
|
|
if 'tool_calls' in message and message['tool_calls']:
|
|
|
|
|
tool_results = []
|
|
|
|
|
|
|
|
|
|
for tool_call in message['tool_calls']:
|
|
|
|
|
func_name = tool_call['function']['name']
|
|
|
|
|
arguments = json.loads(tool_call['function']['arguments'])
|
|
|
|
|
|
|
|
|
|
result = execute_single_tool(assistant, func_name, arguments)
|
|
|
|
|
result = truncate_tool_result(result)
|
|
|
|
|
|
|
|
|
|
status = "success" if result.get("status") == "success" else "error"
|
|
|
|
|
display_tool_call(func_name, arguments, status, result)
|
|
|
|
|
|
|
|
|
|
tool_results.append({
|
|
|
|
|
"tool_call_id": tool_call['id'],
|
|
|
|
|
"role": "tool",
|
|
|
|
|
"content": json.dumps(result)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
for result in tool_results:
|
|
|
|
|
assistant.messages.append(result)
|
|
|
|
|
from pr.core.api import call_api
|
|
|
|
|
from pr.tools.base import get_tools_definition
|
|
|
|
|
follow_up = call_api(
|
|
|
|
|
assistant.messages,
|
|
|
|
|
assistant.model,
|
|
|
|
|
assistant.api_url,
|
|
|
|
|
assistant.api_key,
|
|
|
|
|
assistant.use_tools,
|
|
|
|
|
get_tools_definition(),
|
|
|
|
|
verbose=assistant.verbose
|
|
|
|
|
)
|
|
|
|
|
return process_response_autonomous(assistant, follow_up)
|
|
|
|
|
|
|
|
|
|
content = message.get('content', '')
|
|
|
|
|
from pr.ui import render_markdown
|
|
|
|
|
return render_markdown(content, assistant.syntax_highlighting)
|
|
|
|
|
|
|
|
|
|
def execute_single_tool(assistant, func_name, arguments):
|
|
|
|
|
logger.debug(f"Executing tool in autonomous mode: {func_name}")
|
|
|
|
|
logger.debug(f"Tool arguments: {arguments}")
|
|
|
|
|
|
|
|
|
|
from pr.tools import (
|
|
|
|
|
http_fetch, run_command, run_command_interactive, read_file, write_file,
|
|
|
|
|
list_directory, mkdir, chdir, getpwd, db_set, db_get, db_query,
|
|
|
|
|
web_search, web_search_news, python_exec, index_source_directory,
|
|
|
|
|
search_replace, open_editor, editor_insert_text, editor_replace_text,
|
|
|
|
|
editor_search, close_editor, create_diff, apply_patch, tail_process, kill_process
|
|
|
|
|
)
|
|
|
|
|
from pr.tools.patch import display_file_diff
|
|
|
|
|
from pr.tools.filesystem import display_edit_summary, display_edit_timeline, clear_edit_tracker
|
|
|
|
|
|
|
|
|
|
func_map = {
|
|
|
|
|
'http_fetch': lambda **kw: http_fetch(**kw),
|
|
|
|
|
'run_command': lambda **kw: run_command(**kw),
|
|
|
|
|
'tail_process': lambda **kw: tail_process(**kw),
|
|
|
|
|
'kill_process': lambda **kw: kill_process(**kw),
|
|
|
|
|
'run_command_interactive': lambda **kw: run_command_interactive(**kw),
|
|
|
|
|
'read_file': lambda **kw: read_file(**kw),
|
|
|
|
|
'write_file': lambda **kw: write_file(**kw, db_conn=assistant.db_conn),
|
|
|
|
|
'list_directory': lambda **kw: list_directory(**kw),
|
|
|
|
|
'mkdir': lambda **kw: mkdir(**kw),
|
|
|
|
|
'chdir': lambda **kw: chdir(**kw),
|
|
|
|
|
'getpwd': lambda **kw: getpwd(**kw),
|
|
|
|
|
'db_set': lambda **kw: db_set(**kw, db_conn=assistant.db_conn),
|
|
|
|
|
'db_get': lambda **kw: db_get(**kw, db_conn=assistant.db_conn),
|
|
|
|
|
'db_query': lambda **kw: db_query(**kw, db_conn=assistant.db_conn),
|
|
|
|
|
'web_search': lambda **kw: web_search(**kw),
|
|
|
|
|
'web_search_news': lambda **kw: web_search_news(**kw),
|
|
|
|
|
'python_exec': lambda **kw: python_exec(**kw, python_globals=assistant.python_globals),
|
|
|
|
|
'index_source_directory': lambda **kw: index_source_directory(**kw),
|
|
|
|
|
'search_replace': lambda **kw: search_replace(**kw),
|
|
|
|
|
'open_editor': lambda **kw: open_editor(**kw),
|
|
|
|
|
'editor_insert_text': lambda **kw: editor_insert_text(**kw),
|
|
|
|
|
'editor_replace_text': lambda **kw: editor_replace_text(**kw),
|
|
|
|
|
'editor_search': lambda **kw: editor_search(**kw),
|
|
|
|
|
'close_editor': lambda **kw: close_editor(**kw),
|
|
|
|
|
'create_diff': lambda **kw: create_diff(**kw),
|
|
|
|
|
'apply_patch': lambda **kw: apply_patch(**kw),
|
|
|
|
|
'display_file_diff': lambda **kw: display_file_diff(**kw),
|
|
|
|
|
'display_edit_summary': lambda **kw: display_edit_summary(),
|
|
|
|
|
'display_edit_timeline': lambda **kw: display_edit_timeline(**kw),
|
|
|
|
|
'clear_edit_tracker': lambda **kw: clear_edit_tracker(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if func_name in func_map:
|
|
|
|
|
try:
|
|
|
|
|
result = func_map[func_name](**arguments)
|
|
|
|
|
logger.debug(f"Tool execution result: {str(result)[:200]}...")
|
|
|
|
|
return result
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Tool execution error: {str(e)}")
|
|
|
|
|
return {"status": "error", "error": str(e)}
|
|
|
|
|
else:
|
|
|
|
|
logger.error(f"Unknown function requested: {func_name}")
|
|
|
|
|
return {"status": "error", "error": f"Unknown function: {func_name}"}
|