mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
172 lines
5.6 KiB
Python
172 lines
5.6 KiB
Python
from enum import Enum
|
||
|
||
from openhands.sdk.llm import UNVERIFIED_MODELS_EXCLUDING_BEDROCK, VERIFIED_MODELS
|
||
from prompt_toolkit.completion import FuzzyWordCompleter
|
||
from pydantic import SecretStr
|
||
|
||
from openhands_cli.tui.utils import StepCounter
|
||
from openhands_cli.user_actions.utils import (
|
||
NonEmptyValueValidator,
|
||
cli_confirm,
|
||
cli_text_input,
|
||
)
|
||
|
||
|
||
class SettingsType(Enum):
|
||
BASIC = 'basic'
|
||
ADVANCED = 'advanced'
|
||
|
||
|
||
def settings_type_confirmation(first_time: bool = False) -> SettingsType:
|
||
question = (
|
||
'\nWelcome to OpenHands! Let\'s configure your LLM settings.\n'
|
||
'Choose your preferred setup method:'
|
||
)
|
||
choices = [
|
||
'LLM (Basic)',
|
||
'LLM (Advanced)'
|
||
]
|
||
if not first_time:
|
||
question = 'Which settings would you like to modify?'
|
||
choices.append('Go back')
|
||
|
||
|
||
index = cli_confirm(question, choices, escapable=True)
|
||
|
||
if choices[index] == 'Go back':
|
||
raise KeyboardInterrupt
|
||
|
||
options_map = {0: SettingsType.BASIC, 1: SettingsType.ADVANCED}
|
||
|
||
return options_map.get(index)
|
||
|
||
|
||
def choose_llm_provider(step_counter: StepCounter, escapable=True) -> str:
|
||
question = step_counter.next_step(
|
||
'Select LLM Provider (TAB for options, CTRL-c to cancel): '
|
||
)
|
||
options = (
|
||
list(VERIFIED_MODELS.keys()).copy()
|
||
+ list(UNVERIFIED_MODELS_EXCLUDING_BEDROCK.keys()).copy()
|
||
)
|
||
alternate_option = 'Select another provider'
|
||
|
||
display_options = options[:4] + [alternate_option]
|
||
|
||
index = cli_confirm(question, display_options, escapable=escapable)
|
||
chosen_option = display_options[index]
|
||
if display_options[index] != alternate_option:
|
||
return chosen_option
|
||
|
||
question = step_counter.existing_step(
|
||
'Type LLM Provider (TAB to complete, CTRL-c to cancel): '
|
||
)
|
||
return cli_text_input(
|
||
question, escapable=True, completer=FuzzyWordCompleter(options, WORD=True)
|
||
)
|
||
|
||
|
||
def choose_llm_model(step_counter: StepCounter, provider: str, escapable=True) -> str:
|
||
"""Choose LLM model using spec-driven approach. Return (model, deferred)."""
|
||
|
||
models = VERIFIED_MODELS.get(
|
||
provider, []
|
||
) + UNVERIFIED_MODELS_EXCLUDING_BEDROCK.get(provider, [])
|
||
|
||
if provider == 'openhands':
|
||
question = (
|
||
step_counter.next_step('Select Available OpenHands Model:\n')
|
||
+ 'LLM usage is billed at the providers’ rates with no markup. Details: https://docs.all-hands.dev/usage/llms/openhands-llms'
|
||
)
|
||
else:
|
||
question = step_counter.next_step(
|
||
'Select LLM Model (TAB for options, CTRL-c to cancel): '
|
||
)
|
||
alternate_option = 'Select another model'
|
||
display_options = models[:4] + [alternate_option]
|
||
index = cli_confirm(question, display_options, escapable=escapable)
|
||
chosen_option = display_options[index]
|
||
|
||
if chosen_option != alternate_option:
|
||
return chosen_option
|
||
|
||
question = step_counter.existing_step(
|
||
'Type model id (TAB to complete, CTRL-c to cancel): '
|
||
)
|
||
|
||
return cli_text_input(
|
||
question, escapable=True, completer=FuzzyWordCompleter(models, WORD=True)
|
||
)
|
||
|
||
|
||
def prompt_api_key(
|
||
step_counter: StepCounter,
|
||
provider: str,
|
||
existing_api_key: SecretStr | None = None,
|
||
escapable=True,
|
||
) -> str:
|
||
helper_text = (
|
||
'\nYou can find your OpenHands LLM API Key in the API Keys tab of OpenHands Cloud: '
|
||
'https://app.all-hands.dev/settings/api-keys\n'
|
||
if provider == 'openhands'
|
||
else ''
|
||
)
|
||
|
||
if existing_api_key:
|
||
masked_key = existing_api_key.get_secret_value()[:3] + '***'
|
||
question = f'Enter API Key [{masked_key}] (CTRL-c to cancel, ENTER to keep current, type new to change): '
|
||
# For existing keys, allow empty input to keep current key
|
||
validator = None
|
||
else:
|
||
question = 'Enter API Key (CTRL-c to cancel): '
|
||
# For new keys, require non-empty input
|
||
validator = NonEmptyValueValidator()
|
||
|
||
question = helper_text + step_counter.next_step(question)
|
||
user_input = cli_text_input(
|
||
question, escapable=escapable, validator=validator, is_password=True
|
||
)
|
||
|
||
# If user pressed ENTER with existing key (empty input), return the existing key
|
||
if existing_api_key and not user_input.strip():
|
||
return existing_api_key.get_secret_value()
|
||
|
||
return user_input
|
||
|
||
|
||
# Advanced settings functions
|
||
def prompt_custom_model(step_counter: StepCounter, escapable=True) -> str:
|
||
"""Prompt for custom model name."""
|
||
question = step_counter.next_step('Custom Model (CTRL-c to cancel): ')
|
||
return cli_text_input(question, escapable=escapable)
|
||
|
||
|
||
def prompt_base_url(step_counter: StepCounter, escapable=True) -> str:
|
||
"""Prompt for base URL."""
|
||
question = step_counter.next_step('Base URL (CTRL-c to cancel): ')
|
||
return cli_text_input(
|
||
question, escapable=escapable, validator=NonEmptyValueValidator()
|
||
)
|
||
|
||
|
||
def choose_memory_condensation(step_counter: StepCounter, escapable=True) -> bool:
|
||
"""Choose memory condensation setting."""
|
||
question = step_counter.next_step('Memory Condensation (CTRL-c to cancel): ')
|
||
choices = ['Enable', 'Disable']
|
||
|
||
index = cli_confirm(question, choices, escapable=escapable)
|
||
return index == 0 # True for Enable, False for Disable
|
||
|
||
|
||
def save_settings_confirmation() -> bool:
|
||
"""Prompt user to confirm saving settings."""
|
||
question = 'Save new settings? (They will take effect after restart)'
|
||
discard = 'No, discard'
|
||
options = ['Yes, save', discard]
|
||
|
||
index = cli_confirm(question, options, escapable=True)
|
||
if options[index] == discard:
|
||
raise KeyboardInterrupt
|
||
|
||
return options[index]
|