Backup-bdg/OpenHands
0
1from prompt_toolkit import PromptSession, print_formatted_text2from prompt_toolkit.completion import FuzzyWordCompleter3from prompt_toolkit.formatted_text import HTML4from prompt_toolkit.shortcuts import print_container5from prompt_toolkit.widgets import Frame, TextArea6from pydantic import SecretStr7 8from openhands.cli.tui import (9 COLOR_GREY,10 UserCancelledError,11 cli_confirm,12 kb_cancel,13)14from openhands.cli.utils import (15 VERIFIED_ANTHROPIC_MODELS,16 VERIFIED_OPENAI_MODELS,17 VERIFIED_PROVIDERS,18 organize_models_and_providers,19)20from openhands.controller.agent import Agent21from openhands.core.config import OpenHandsConfig22from openhands.core.config.condenser_config import NoOpCondenserConfig23from openhands.core.config.utils import OH_DEFAULT_AGENT24from openhands.memory.condenser.impl.llm_summarizing_condenser import (25 LLMSummarizingCondenserConfig,26)27from openhands.storage.data_models.settings import Settings28from openhands.storage.settings.file_settings_store import FileSettingsStore29from openhands.utils.llm import get_supported_llm_models30 31 32def display_settings(config: OpenHandsConfig) -> None:33 llm_config = config.get_llm_config()34 advanced_llm_settings = True if llm_config.base_url else False35 36 # Prepare labels and values based on settings37 labels_and_values = []38 if not advanced_llm_settings:39 # Attempt to determine provider, fallback if not directly available40 provider = getattr(41 llm_config,42 'provider',43 llm_config.model.split('/')[0] if '/' in llm_config.model else 'Unknown',44 )45 labels_and_values.extend(46 [47 (' LLM Provider', str(provider)),48 (' LLM Model', str(llm_config.model)),49 (' API Key', '********' if llm_config.api_key else 'Not Set'),50 ]51 )52 else:53 labels_and_values.extend(54 [55 (' Custom Model', str(llm_config.model)),56 (' Base URL', str(llm_config.base_url)),57 (' API Key', '********' if llm_config.api_key else 'Not Set'),58 ]59 )60 61 # Common settings62 labels_and_values.extend(63 [64 (' Agent', str(config.default_agent)),65 (66 ' Confirmation Mode',67 'Enabled' if config.security.confirmation_mode else 'Disabled',68 ),69 (70 ' Memory Condensation',71 'Enabled' if config.enable_default_condenser else 'Disabled',72 ),73 ]74 )75 76 # Calculate max widths for alignment77 # Ensure values are strings for len() calculation78 str_labels_and_values = [(label, str(value)) for label, value in labels_and_values]79 max_label_width = (80 max(len(label) for label, _ in str_labels_and_values)81 if str_labels_and_values82 else 083 )84 85 # Construct the summary text with aligned columns86 settings_lines = [87 f'{label + ":":<{max_label_width + 1}} {value:<}' # Changed value alignment to left (<)88 for label, value in str_labels_and_values89 ]90 settings_text = '\n'.join(settings_lines)91 92 container = Frame(93 TextArea(94 text=settings_text,95 read_only=True,96 style=COLOR_GREY,97 wrap_lines=True,98 ),99 title='Settings',100 style=f'fg:{COLOR_GREY}',101 )102 103 print_container(container)104 105 106async def get_validated_input(107 session: PromptSession,108 prompt_text: str,109 completer=None,110 validator=None,111 error_message: str = 'Input cannot be empty',112) -> str:113 session.completer = completer114 value = None115 116 while True:117 value = await session.prompt_async(prompt_text)118 119 if validator:120 is_valid = validator(value)121 if not is_valid:122 print_formatted_text('')123 print_formatted_text(HTML(f'<grey>{error_message}: {value}</grey>'))124 print_formatted_text('')125 continue126 elif not value:127 print_formatted_text('')128 print_formatted_text(HTML(f'<grey>{error_message}</grey>'))129 print_formatted_text('')130 continue131 132 break133 134 return value135 136 137def save_settings_confirmation() -> bool:138 return (139 cli_confirm(140 '\nSave new settings? (They will take effect after restart)',141 ['Yes, save', 'No, discard'],142 )143 == 0144 )145 146 147async def modify_llm_settings_basic(148 config: OpenHandsConfig, settings_store: FileSettingsStore149) -> None:150 model_list = get_supported_llm_models(config)151 organized_models = organize_models_and_providers(model_list)152 153 provider_list = list(organized_models.keys())154 verified_providers = [p for p in VERIFIED_PROVIDERS if p in provider_list]155 provider_list = [p for p in provider_list if p not in verified_providers]156 provider_list = verified_providers + provider_list157 158 provider_completer = FuzzyWordCompleter(provider_list)159 session = PromptSession(key_bindings=kb_cancel())160 161 # Set default provider - use the first available provider from the list162 provider = provider_list[0] if provider_list else 'openai'163 model = None164 api_key = None165 166 try:167 # Show the default provider but allow changing it168 print_formatted_text(169 HTML(f'\n<grey>Default provider: </grey><green>{provider}</green>')170 )171 change_provider = (172 cli_confirm(173 'Do you want to use a different provider?',174 [f'Use {provider}', 'Select another provider'],175 )176 == 1177 )178 179 if change_provider:180 # Define a validator function that prints an error message181 def provider_validator(x):182 is_valid = x in organized_models183 if not is_valid:184 print_formatted_text(185 HTML('<grey>Invalid provider selected: {}</grey>'.format(x))186 )187 return is_valid188 189 provider = await get_validated_input(190 session,191 '(Step 1/3) Select LLM Provider (TAB for options, CTRL-c to cancel): ',192 completer=provider_completer,193 validator=provider_validator,194 error_message='Invalid provider selected',195 )196 197 # Make sure the provider exists in organized_models198 if provider not in organized_models:199 # If the provider doesn't exist, use the first available provider200 provider = (201 next(iter(organized_models.keys())) if organized_models else 'openai'202 )203 204 provider_models = organized_models[provider]['models']205 if provider == 'openai':206 provider_models = [207 m for m in provider_models if m not in VERIFIED_OPENAI_MODELS208 ]209 provider_models = VERIFIED_OPENAI_MODELS + provider_models210 if provider == 'anthropic':211 provider_models = [212 m for m in provider_models if m not in VERIFIED_ANTHROPIC_MODELS213 ]214 provider_models = VERIFIED_ANTHROPIC_MODELS + provider_models215 216 # Set default model to the first model in the list217 default_model = provider_models[0] if provider_models else 'gpt-4'218 219 # Show the default model but allow changing it220 print_formatted_text(221 HTML(f'\n<grey>Default model: </grey><green>{default_model}</green>')222 )223 change_model = (224 cli_confirm(225 'Do you want to use a different model?',226 [f'Use {default_model}', 'Select another model'],227 )228 == 1229 )230 231 if change_model:232 model_completer = FuzzyWordCompleter(provider_models)233 234 # Define a validator function that prints an error message235 def model_validator(x):236 is_valid = x in provider_models237 if not is_valid:238 print_formatted_text(239 HTML(240 f'<grey>Invalid model selected for provider {provider}: {x}</grey>'241 )242 )243 return is_valid244 245 model = await get_validated_input(246 session,247 '(Step 2/3) Select LLM Model (TAB for options, CTRL-c to cancel): ',248 completer=model_completer,249 validator=model_validator,250 error_message=f'Invalid model selected for provider {provider}',251 )252 else:253 # Use the default model254 model = default_model255 256 api_key = await get_validated_input(257 session,258 '(Step 3/3) Enter API Key (CTRL-c to cancel): ',259 error_message='API Key cannot be empty',260 )261 262 except (263 UserCancelledError,264 KeyboardInterrupt,265 EOFError,266 ):267 return # Return on exception268 269 # The try-except block above ensures we either have valid inputs or we've already returned270 # No need to check for None values here271 272 save_settings = save_settings_confirmation()273 274 if not save_settings:275 return276 277 llm_config = config.get_llm_config()278 llm_config.model = f'{provider}{organized_models[provider]["separator"]}{model}'279 llm_config.api_key = SecretStr(api_key)280 llm_config.base_url = None281 config.set_llm_config(llm_config)282 283 config.default_agent = OH_DEFAULT_AGENT284 config.enable_default_condenser = True285 286 agent_config = config.get_agent_config(config.default_agent)287 agent_config.condenser = LLMSummarizingCondenserConfig(288 llm_config=llm_config,289 type='llm',290 )291 config.set_agent_config(agent_config, config.default_agent)292 293 settings = await settings_store.load()294 if not settings:295 settings = Settings()296 297 settings.llm_model = f'{provider}{organized_models[provider]["separator"]}{model}'298 settings.llm_api_key = SecretStr(api_key)299 settings.llm_base_url = None300 settings.agent = OH_DEFAULT_AGENT301 settings.enable_default_condenser = True302 303 await settings_store.store(settings)304 305 306async def modify_llm_settings_advanced(307 config: OpenHandsConfig, settings_store: FileSettingsStore308) -> None:309 session = PromptSession(key_bindings=kb_cancel())310 311 custom_model = None312 base_url = None313 api_key = None314 agent = None315 316 try:317 custom_model = await get_validated_input(318 session,319 '(Step 1/6) Custom Model (CTRL-c to cancel): ',320 error_message='Custom Model cannot be empty',321 )322 323 base_url = await get_validated_input(324 session,325 '(Step 2/6) Base URL (CTRL-c to cancel): ',326 error_message='Base URL cannot be empty',327 )328 329 api_key = await get_validated_input(330 session,331 '(Step 3/6) API Key (CTRL-c to cancel): ',332 error_message='API Key cannot be empty',333 )334 335 agent_list = Agent.list_agents()336 agent_completer = FuzzyWordCompleter(agent_list)337 agent = await get_validated_input(338 session,339 '(Step 4/6) Agent (TAB for options, CTRL-c to cancel): ',340 completer=agent_completer,341 validator=lambda x: x in agent_list,342 error_message='Invalid agent selected',343 )344 345 enable_confirmation_mode = (346 cli_confirm(347 question='(Step 5/6) Confirmation Mode (CTRL-c to cancel):',348 choices=['Enable', 'Disable'],349 )350 == 0351 )352 353 enable_memory_condensation = (354 cli_confirm(355 question='(Step 6/6) Memory Condensation (CTRL-c to cancel):',356 choices=['Enable', 'Disable'],357 )358 == 0359 )360 361 except (362 UserCancelledError,363 KeyboardInterrupt,364 EOFError,365 ):366 return # Return on exception367 368 # The try-except block above ensures we either have valid inputs or we've already returned369 # No need to check for None values here370 371 save_settings = save_settings_confirmation()372 373 if not save_settings:374 return375 376 llm_config = config.get_llm_config()377 llm_config.model = custom_model378 llm_config.base_url = base_url379 llm_config.api_key = SecretStr(api_key)380 config.set_llm_config(llm_config)381 382 config.default_agent = agent383 384 config.security.confirmation_mode = enable_confirmation_mode385 386 agent_config = config.get_agent_config(config.default_agent)387 if enable_memory_condensation:388 agent_config.condenser = LLMSummarizingCondenserConfig(389 llm_config=llm_config,390 type='llm',391 )392 else:393 agent_config.condenser = NoOpCondenserConfig(type='noop')394 config.set_agent_config(agent_config)395 396 settings = await settings_store.load()397 if not settings:398 settings = Settings()399 400 settings.llm_model = custom_model401 settings.llm_api_key = SecretStr(api_key)402 settings.llm_base_url = base_url403 settings.agent = agent404 settings.confirmation_mode = enable_confirmation_mode405 settings.enable_default_condenser = enable_memory_condensation406 407 await settings_store.store(settings)408 