Spaces:
Build error
Build error
import asyncio | |
from pathlib import Path | |
from prompt_toolkit import print_formatted_text | |
from prompt_toolkit.shortcuts import clear, print_container | |
from prompt_toolkit.widgets import Frame, TextArea | |
from openhands.cli.settings import ( | |
display_settings, | |
modify_llm_settings_advanced, | |
modify_llm_settings_basic, | |
) | |
from openhands.cli.tui import ( | |
COLOR_GREY, | |
UsageMetrics, | |
cli_confirm, | |
display_help, | |
display_shutdown_message, | |
display_status, | |
) | |
from openhands.cli.utils import ( | |
add_local_config_trusted_dir, | |
get_local_config_trusted_dirs, | |
read_file, | |
write_to_file, | |
) | |
from openhands.core.config import ( | |
OpenHandsConfig, | |
) | |
from openhands.core.schema import AgentState | |
from openhands.events import EventSource | |
from openhands.events.action import ( | |
ChangeAgentStateAction, | |
MessageAction, | |
) | |
from openhands.events.stream import EventStream | |
from openhands.storage.settings.file_settings_store import FileSettingsStore | |
async def handle_commands( | |
command: str, | |
event_stream: EventStream, | |
usage_metrics: UsageMetrics, | |
sid: str, | |
config: OpenHandsConfig, | |
current_dir: str, | |
settings_store: FileSettingsStore, | |
) -> tuple[bool, bool, bool]: | |
close_repl = False | |
reload_microagents = False | |
new_session_requested = False | |
if command == '/exit': | |
close_repl = handle_exit_command( | |
event_stream, | |
usage_metrics, | |
sid, | |
) | |
elif command == '/help': | |
handle_help_command() | |
elif command == '/init': | |
close_repl, reload_microagents = await handle_init_command( | |
config, event_stream, current_dir | |
) | |
elif command == '/status': | |
handle_status_command(usage_metrics, sid) | |
elif command == '/new': | |
close_repl, new_session_requested = handle_new_command( | |
event_stream, usage_metrics, sid | |
) | |
elif command == '/settings': | |
await handle_settings_command(config, settings_store) | |
elif command == '/resume': | |
close_repl, new_session_requested = await handle_resume_command(event_stream) | |
else: | |
close_repl = True | |
action = MessageAction(content=command) | |
event_stream.add_event(action, EventSource.USER) | |
return close_repl, reload_microagents, new_session_requested | |
def handle_exit_command( | |
event_stream: EventStream, usage_metrics: UsageMetrics, sid: str | |
) -> bool: | |
close_repl = False | |
confirm_exit = ( | |
cli_confirm('\nTerminate session?', ['Yes, proceed', 'No, dismiss']) == 0 | |
) | |
if confirm_exit: | |
event_stream.add_event( | |
ChangeAgentStateAction(AgentState.STOPPED), | |
EventSource.ENVIRONMENT, | |
) | |
display_shutdown_message(usage_metrics, sid) | |
close_repl = True | |
return close_repl | |
def handle_help_command() -> None: | |
display_help() | |
async def handle_init_command( | |
config: OpenHandsConfig, event_stream: EventStream, current_dir: str | |
) -> tuple[bool, bool]: | |
REPO_MD_CREATE_PROMPT = """ | |
Please explore this repository. Create the file .openhands/microagents/repo.md with: | |
- A description of the project | |
- An overview of the file structure | |
- Any information on how to run tests or other relevant commands | |
- Any other information that would be helpful to a brand new developer | |
Keep it short--just a few paragraphs will do. | |
""" | |
close_repl = False | |
reload_microagents = False | |
if config.runtime == 'local': | |
init_repo = await init_repository(current_dir) | |
if init_repo: | |
event_stream.add_event( | |
MessageAction(content=REPO_MD_CREATE_PROMPT), | |
EventSource.USER, | |
) | |
reload_microagents = True | |
close_repl = True | |
else: | |
print_formatted_text( | |
'\nRepository initialization through the CLI is only supported for local runtime.\n' | |
) | |
return close_repl, reload_microagents | |
def handle_status_command(usage_metrics: UsageMetrics, sid: str) -> None: | |
display_status(usage_metrics, sid) | |
def handle_new_command( | |
event_stream: EventStream, usage_metrics: UsageMetrics, sid: str | |
) -> tuple[bool, bool]: | |
close_repl = False | |
new_session_requested = False | |
new_session_requested = ( | |
cli_confirm( | |
'\nCurrent session will be terminated and you will lose the conversation history.\n\nContinue?', | |
['Yes, proceed', 'No, dismiss'], | |
) | |
== 0 | |
) | |
if new_session_requested: | |
close_repl = True | |
new_session_requested = True | |
event_stream.add_event( | |
ChangeAgentStateAction(AgentState.STOPPED), | |
EventSource.ENVIRONMENT, | |
) | |
display_shutdown_message(usage_metrics, sid) | |
return close_repl, new_session_requested | |
async def handle_settings_command( | |
config: OpenHandsConfig, | |
settings_store: FileSettingsStore, | |
) -> None: | |
display_settings(config) | |
modify_settings = cli_confirm( | |
'\nWhich settings would you like to modify?', | |
[ | |
'Basic', | |
'Advanced', | |
'Go back', | |
], | |
) | |
if modify_settings == 0: | |
await modify_llm_settings_basic(config, settings_store) | |
elif modify_settings == 1: | |
await modify_llm_settings_advanced(config, settings_store) | |
# FIXME: Currently there's an issue with the actual 'resume' behavior. | |
# Setting the agent state to RUNNING will currently freeze the agent without continuing with the rest of the task. | |
# This is a workaround to handle the resume command for the time being. Replace user message with the state change event once the issue is fixed. | |
async def handle_resume_command( | |
event_stream: EventStream, | |
) -> tuple[bool, bool]: | |
close_repl = True | |
new_session_requested = False | |
event_stream.add_event( | |
MessageAction(content='continue'), | |
EventSource.USER, | |
) | |
# event_stream.add_event( | |
# ChangeAgentStateAction(AgentState.RUNNING), | |
# EventSource.ENVIRONMENT, | |
# ) | |
return close_repl, new_session_requested | |
async def init_repository(current_dir: str) -> bool: | |
repo_file_path = Path(current_dir) / '.openhands' / 'microagents' / 'repo.md' | |
init_repo = False | |
if repo_file_path.exists(): | |
try: | |
# Path.exists() ensures repo_file_path is not None, so we can safely pass it to read_file | |
content = await asyncio.get_event_loop().run_in_executor( | |
None, read_file, repo_file_path | |
) | |
print_formatted_text( | |
'Repository instructions file (repo.md) already exists.\n' | |
) | |
container = Frame( | |
TextArea( | |
text=content, | |
read_only=True, | |
style=COLOR_GREY, | |
wrap_lines=True, | |
), | |
title='Repository Instructions (repo.md)', | |
style=f'fg:{COLOR_GREY}', | |
) | |
print_container(container) | |
print_formatted_text('') # Add a newline after the frame | |
init_repo = ( | |
cli_confirm( | |
'Do you want to re-initialize?', | |
['Yes, re-initialize', 'No, dismiss'], | |
) | |
== 0 | |
) | |
if init_repo: | |
write_to_file(repo_file_path, '') | |
except Exception: | |
print_formatted_text('Error reading repository instructions file (repo.md)') | |
init_repo = False | |
else: | |
print_formatted_text( | |
'\nRepository instructions file will be created by exploring the repository.\n' | |
) | |
init_repo = ( | |
cli_confirm( | |
'Do you want to proceed?', | |
['Yes, create', 'No, dismiss'], | |
) | |
== 0 | |
) | |
return init_repo | |
def check_folder_security_agreement(config: OpenHandsConfig, current_dir: str) -> bool: | |
# Directories trusted by user for the CLI to use as workspace | |
# Config from ~/.openhands/config.toml overrides the app config | |
app_config_trusted_dirs = config.sandbox.trusted_dirs | |
local_config_trusted_dirs = get_local_config_trusted_dirs() | |
trusted_dirs = local_config_trusted_dirs | |
if not local_config_trusted_dirs: | |
trusted_dirs = app_config_trusted_dirs | |
is_trusted = current_dir in trusted_dirs | |
if not is_trusted: | |
security_frame = Frame( | |
TextArea( | |
text=( | |
f' Do you trust the files in this folder?\n\n' | |
f' {current_dir}\n\n' | |
' OpenHands may read and execute files in this folder with your permission.' | |
), | |
style=COLOR_GREY, | |
read_only=True, | |
wrap_lines=True, | |
), | |
style=f'fg:{COLOR_GREY}', | |
) | |
clear() | |
print_container(security_frame) | |
print_formatted_text('') | |
confirm = ( | |
cli_confirm('Do you wish to continue?', ['Yes, proceed', 'No, exit']) == 0 | |
) | |
if confirm: | |
add_local_config_trusted_dir(current_dir) | |
return confirm | |
return True | |