openhands-sdk 1.49.5__tar.gz → 1.49.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/PKG-INFO +1 -1
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/local_conversation.py +31 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/executor.py +4 -3
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/__init__.py +10 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm.py +150 -0
- openhands_sdk-1.49.6/openhands/sdk/llm/meta_profile_store.py +274 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_features.py +0 -1
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/verified_models.py +7 -3
- openhands_sdk-1.49.6/openhands/sdk/mcp/oauth.py +85 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/utils.py +3 -2
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/api_models.py +9 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/model.py +66 -1
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/__init__.py +11 -0
- openhands_sdk-1.49.6/openhands/sdk/tool/builtins/classify_and_switch_llm.py +503 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/PKG-INFO +1 -1
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/SOURCES.txt +6 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/pyproject.toml +1 -1
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_agent.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_file_credentials.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/agent.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/stream_context.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/sections/static.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/request.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_profile_store.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/agent_profile.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/resolver.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/acp_install_catalog.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/masking.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.49.
|
|
3
|
+
Version: 1.49.6
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
5
|
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
6
|
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|
{openhands_sdk-1.49.5 → openhands_sdk-1.49.6}/openhands/sdk/conversation/impl/local_conversation.py
RENAMED
|
@@ -55,6 +55,7 @@ from openhands.sdk.hooks import HookConfig, HookEventProcessor, create_hook_call
|
|
|
55
55
|
from openhands.sdk.io import FileStore, LocalFileStore
|
|
56
56
|
from openhands.sdk.llm import LLM, Message, TextContent, content_to_str
|
|
57
57
|
from openhands.sdk.llm.auth.openai import create_subscription_llm_from_config
|
|
58
|
+
from openhands.sdk.llm.exceptions import LLMAuthenticationError
|
|
58
59
|
from openhands.sdk.llm.llm import LLMCallContext
|
|
59
60
|
from openhands.sdk.llm.llm_profile_store import LLMProfileStore
|
|
60
61
|
from openhands.sdk.llm.llm_registry import LLMRegistry
|
|
@@ -2040,6 +2041,21 @@ class LocalConversation(BaseConversation):
|
|
|
2040
2041
|
)
|
|
2041
2042
|
)
|
|
2042
2043
|
break
|
|
2044
|
+
except LLMAuthenticationError as e:
|
|
2045
|
+
with self._state:
|
|
2046
|
+
self._state.execution_status = ConversationExecutionStatus.ERROR
|
|
2047
|
+
self._on_event(
|
|
2048
|
+
ConversationErrorEvent(
|
|
2049
|
+
source="environment",
|
|
2050
|
+
code="LLMAuthenticationError",
|
|
2051
|
+
detail=(
|
|
2052
|
+
"Your LLM API key appears to be invalid or has expired."
|
|
2053
|
+
),
|
|
2054
|
+
)
|
|
2055
|
+
)
|
|
2056
|
+
raise ConversationRunError(
|
|
2057
|
+
self._state.id, e, persistence_dir=self._state.persistence_dir
|
|
2058
|
+
) from e
|
|
2043
2059
|
except Exception as e:
|
|
2044
2060
|
with self._state:
|
|
2045
2061
|
self._state.execution_status = ConversationExecutionStatus.ERROR
|
|
@@ -2540,6 +2556,21 @@ class LocalConversation(BaseConversation):
|
|
|
2540
2556
|
|
|
2541
2557
|
self._state.execution_status = ConversationExecutionStatus.PAUSED
|
|
2542
2558
|
self._on_event(InterruptEvent())
|
|
2559
|
+
except LLMAuthenticationError as e:
|
|
2560
|
+
with self._state:
|
|
2561
|
+
self._state.execution_status = ConversationExecutionStatus.ERROR
|
|
2562
|
+
self._on_event(
|
|
2563
|
+
ConversationErrorEvent(
|
|
2564
|
+
source="environment",
|
|
2565
|
+
code="LLMAuthenticationError",
|
|
2566
|
+
detail=(
|
|
2567
|
+
"Your LLM API key appears to be invalid or has expired."
|
|
2568
|
+
),
|
|
2569
|
+
)
|
|
2570
|
+
)
|
|
2571
|
+
raise ConversationRunError(
|
|
2572
|
+
self._state.id, e, persistence_dir=self._state.persistence_dir
|
|
2573
|
+
) from e
|
|
2543
2574
|
except Exception as e:
|
|
2544
2575
|
with self._state:
|
|
2545
2576
|
updated_agent_state = dict(self._state.agent_state)
|
|
@@ -566,9 +566,10 @@ class HookExecutor:
|
|
|
566
566
|
try:
|
|
567
567
|
output_data = json.loads(result.stdout)
|
|
568
568
|
if isinstance(output_data, dict):
|
|
569
|
-
#
|
|
570
|
-
|
|
571
|
-
|
|
569
|
+
# Non-string values represent no decision.
|
|
570
|
+
decision_value = output_data.get("decision")
|
|
571
|
+
if isinstance(decision_value, str):
|
|
572
|
+
decision_str = decision_value.lower()
|
|
572
573
|
if decision_str == "allow":
|
|
573
574
|
hook_result.decision = HookDecision.ALLOW
|
|
574
575
|
elif decision_str == "deny":
|
|
@@ -28,6 +28,12 @@ from openhands.sdk.llm.message import (
|
|
|
28
28
|
ThinkingBlock,
|
|
29
29
|
content_to_str,
|
|
30
30
|
)
|
|
31
|
+
from openhands.sdk.llm.meta_profile_store import (
|
|
32
|
+
MetaProfile,
|
|
33
|
+
MetaProfileClass,
|
|
34
|
+
MetaProfileLimitExceeded,
|
|
35
|
+
MetaProfileStore,
|
|
36
|
+
)
|
|
31
37
|
from openhands.sdk.llm.router import RouterLLM
|
|
32
38
|
from openhands.sdk.llm.streaming import (
|
|
33
39
|
AsyncTokenCallbackType,
|
|
@@ -61,6 +67,10 @@ __all__ = [
|
|
|
61
67
|
"LLMProfileLoader",
|
|
62
68
|
"LLMProfileMutator",
|
|
63
69
|
"LLMProfileStore",
|
|
70
|
+
"MetaProfile",
|
|
71
|
+
"MetaProfileClass",
|
|
72
|
+
"MetaProfileLimitExceeded",
|
|
73
|
+
"MetaProfileStore",
|
|
64
74
|
"RouterLLM",
|
|
65
75
|
"RegistryEvent",
|
|
66
76
|
# Messages
|
|
@@ -96,6 +96,7 @@ from openhands.sdk.llm.exceptions import (
|
|
|
96
96
|
LLMNoResponseError,
|
|
97
97
|
is_prompt_cache_too_small,
|
|
98
98
|
is_quota_exhaustion_error,
|
|
99
|
+
looks_like_auth_error,
|
|
99
100
|
map_provider_exception,
|
|
100
101
|
)
|
|
101
102
|
|
|
@@ -661,6 +662,19 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
661
662
|
# a result, so the read/check and store are kept atomic. ClassVar (shared);
|
|
662
663
|
# critical sections are tiny and never cover network I/O.
|
|
663
664
|
_runtime_metadata_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
665
|
+
# Optional callback that returns a freshly-resolved API key. When set, an
|
|
666
|
+
# authentication failure (HTTP 401, e.g. a rotated/healed managed proxy key
|
|
667
|
+
# that LiteLLM reports as ``token_not_found_in_db``) triggers a single
|
|
668
|
+
# re-resolve of the key followed by one retry of the call. This is a
|
|
669
|
+
# near-term mitigation for stale credentials reaching a sandbox runtime
|
|
670
|
+
# agent; the durable fix is reference-only credential delivery (#4288).
|
|
671
|
+
# Held as a PrivateAttr so it is never serialized into conversation state.
|
|
672
|
+
_api_key_refresh_hook: Callable[[], str | SecretStr | None] | None = PrivateAttr(
|
|
673
|
+
default=None
|
|
674
|
+
)
|
|
675
|
+
# Recursion guard: set on the refreshed copy so a still-failing key does not
|
|
676
|
+
# loop. One refresh + one retry per call chain.
|
|
677
|
+
_auth_refresh_attempted: bool = PrivateAttr(default=False)
|
|
664
678
|
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
665
679
|
extra="ignore", arbitrary_types_allowed=True
|
|
666
680
|
)
|
|
@@ -1011,6 +1025,72 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
1011
1025
|
raise mapped from error
|
|
1012
1026
|
raise
|
|
1013
1027
|
|
|
1028
|
+
# =========================================================================
|
|
1029
|
+
# Authentication refresh (refresh-on-401)
|
|
1030
|
+
# =========================================================================
|
|
1031
|
+
def set_api_key_refresh_hook(
|
|
1032
|
+
self, hook: Callable[[], str | SecretStr | None] | None
|
|
1033
|
+
) -> None:
|
|
1034
|
+
"""Register a callback that re-resolves this LLM's API key on a 401.
|
|
1035
|
+
|
|
1036
|
+
When a completion/responses call fails with an authentication error,
|
|
1037
|
+
the hook is invoked once to obtain a fresh key; if it returns a new
|
|
1038
|
+
value the call is retried a single time with that key. This mitigates
|
|
1039
|
+
stale managed/proxy credentials reaching a sandbox runtime agent (e.g.
|
|
1040
|
+
after a server-side managed-key rotation or heal), where the first call
|
|
1041
|
+
would otherwise fail with ``401 token_not_found_in_db``.
|
|
1042
|
+
|
|
1043
|
+
The hook must be non-blocking / cheap; on the async paths it is invoked
|
|
1044
|
+
in a worker thread. It is stored as a private attribute and is never
|
|
1045
|
+
serialized into conversation state.
|
|
1046
|
+
"""
|
|
1047
|
+
self._api_key_refresh_hook = hook
|
|
1048
|
+
|
|
1049
|
+
def _resolve_refreshed_api_key(self, error: Exception) -> SecretStr | None:
|
|
1050
|
+
"""Return a fresh API key to retry with, or ``None`` to skip refresh.
|
|
1051
|
+
|
|
1052
|
+
Returns ``None`` unless every condition holds: the error looks like an
|
|
1053
|
+
authentication failure, a refresh hook is configured, no refresh has
|
|
1054
|
+
already been attempted in this call chain, this LLM uses ``api_key``
|
|
1055
|
+
auth, and the hook yields a key that differs from the current one.
|
|
1056
|
+
"""
|
|
1057
|
+
if self._auth_refresh_attempted:
|
|
1058
|
+
return None
|
|
1059
|
+
if self._api_key_refresh_hook is None:
|
|
1060
|
+
return None
|
|
1061
|
+
if self.auth_type != "api_key":
|
|
1062
|
+
return None
|
|
1063
|
+
if not looks_like_auth_error(error):
|
|
1064
|
+
return None
|
|
1065
|
+
try:
|
|
1066
|
+
new_key = self._api_key_refresh_hook()
|
|
1067
|
+
except Exception:
|
|
1068
|
+
# A flaky hook must not mask the original authentication error;
|
|
1069
|
+
# log and skip the refresh so the caller sees the real 401.
|
|
1070
|
+
logger.warning(
|
|
1071
|
+
"API key refresh hook raised; skipping refresh and "
|
|
1072
|
+
"surfacing the original authentication error.",
|
|
1073
|
+
exc_info=True,
|
|
1074
|
+
)
|
|
1075
|
+
return None
|
|
1076
|
+
if new_key is None:
|
|
1077
|
+
return None
|
|
1078
|
+
new_secret = new_key if isinstance(new_key, SecretStr) else SecretStr(new_key)
|
|
1079
|
+
if not new_secret.get_secret_value():
|
|
1080
|
+
return None
|
|
1081
|
+
current = self._get_api_key_value()
|
|
1082
|
+
if current is not None and current == new_secret.get_secret_value():
|
|
1083
|
+
# A refresh that returns the same (still-rejected) key is useless;
|
|
1084
|
+
# skip the retry and surface the original error.
|
|
1085
|
+
return None
|
|
1086
|
+
return new_secret
|
|
1087
|
+
|
|
1088
|
+
def _auth_refreshed_llm(self, new_api_key: SecretStr) -> LLM:
|
|
1089
|
+
"""Copy this LLM with a refreshed key and the recursion guard set."""
|
|
1090
|
+
refreshed = self.model_copy(update={"api_key": new_api_key})
|
|
1091
|
+
refreshed._auth_refresh_attempted = True
|
|
1092
|
+
return refreshed
|
|
1093
|
+
|
|
1014
1094
|
# =========================================================================
|
|
1015
1095
|
# Shared helpers for completion / acompletion / responses / aresponses
|
|
1016
1096
|
# =========================================================================
|
|
@@ -1668,6 +1748,21 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
1668
1748
|
on_token=on_token,
|
|
1669
1749
|
**_caller_kwargs,
|
|
1670
1750
|
)
|
|
1751
|
+
# If the credential was rejected (401) and a refresh hook can supply
|
|
1752
|
+
# a fresh key, re-resolve it and retry the call exactly once.
|
|
1753
|
+
refreshed_key = self._resolve_refreshed_api_key(e)
|
|
1754
|
+
if refreshed_key is not None:
|
|
1755
|
+
logger.warning(
|
|
1756
|
+
"Authentication error; refreshing API key and retrying once."
|
|
1757
|
+
)
|
|
1758
|
+
return self._auth_refreshed_llm(refreshed_key).completion(
|
|
1759
|
+
messages,
|
|
1760
|
+
tools,
|
|
1761
|
+
add_security_risk_prediction=add_security_risk_prediction,
|
|
1762
|
+
on_token=on_token,
|
|
1763
|
+
call_context=call_context,
|
|
1764
|
+
**_caller_kwargs,
|
|
1765
|
+
)
|
|
1671
1766
|
return self._handle_error(
|
|
1672
1767
|
e,
|
|
1673
1768
|
lambda fb: fb.completion(
|
|
@@ -1772,6 +1867,24 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
1772
1867
|
on_token=on_token,
|
|
1773
1868
|
**_caller_kwargs,
|
|
1774
1869
|
)
|
|
1870
|
+
# If the credential was rejected (401) and a refresh hook can supply
|
|
1871
|
+
# a fresh key, re-resolve it (off the event loop) and retry once.
|
|
1872
|
+
loop = asyncio.get_running_loop()
|
|
1873
|
+
refreshed_key = await loop.run_in_executor(
|
|
1874
|
+
None, self._resolve_refreshed_api_key, e
|
|
1875
|
+
)
|
|
1876
|
+
if refreshed_key is not None:
|
|
1877
|
+
logger.warning(
|
|
1878
|
+
"Authentication error; refreshing API key and retrying once."
|
|
1879
|
+
)
|
|
1880
|
+
return await self._auth_refreshed_llm(refreshed_key).acompletion(
|
|
1881
|
+
messages,
|
|
1882
|
+
tools,
|
|
1883
|
+
add_security_risk_prediction=add_security_risk_prediction,
|
|
1884
|
+
on_token=on_token,
|
|
1885
|
+
call_context=call_context,
|
|
1886
|
+
**_caller_kwargs,
|
|
1887
|
+
)
|
|
1775
1888
|
# Fallback is synchronous; cast the token callback since the
|
|
1776
1889
|
# fallback LLM's sync path accepts TokenCallbackType.
|
|
1777
1890
|
_fb_token = cast("TokenCallbackType | None", on_token)
|
|
@@ -1938,6 +2051,23 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
1938
2051
|
on_token=on_token,
|
|
1939
2052
|
**_caller_kwargs,
|
|
1940
2053
|
)
|
|
2054
|
+
# If the credential was rejected (401) and a refresh hook can supply
|
|
2055
|
+
# a fresh key, re-resolve it and retry the call exactly once.
|
|
2056
|
+
refreshed_key = self._resolve_refreshed_api_key(e)
|
|
2057
|
+
if refreshed_key is not None:
|
|
2058
|
+
logger.warning(
|
|
2059
|
+
"Authentication error; refreshing API key and retrying once."
|
|
2060
|
+
)
|
|
2061
|
+
return self._auth_refreshed_llm(refreshed_key).responses(
|
|
2062
|
+
messages,
|
|
2063
|
+
tools,
|
|
2064
|
+
include,
|
|
2065
|
+
store,
|
|
2066
|
+
add_security_risk_prediction=add_security_risk_prediction,
|
|
2067
|
+
on_token=on_token,
|
|
2068
|
+
call_context=call_context,
|
|
2069
|
+
**_caller_kwargs,
|
|
2070
|
+
)
|
|
1941
2071
|
return self._handle_error(
|
|
1942
2072
|
e,
|
|
1943
2073
|
lambda fb: fb.responses(
|
|
@@ -2115,6 +2245,26 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
|
|
|
2115
2245
|
on_token=on_token,
|
|
2116
2246
|
**_caller_kwargs,
|
|
2117
2247
|
)
|
|
2248
|
+
# If the credential was rejected (401) and a refresh hook can supply
|
|
2249
|
+
# a fresh key, re-resolve it (off the event loop) and retry once.
|
|
2250
|
+
loop = asyncio.get_running_loop()
|
|
2251
|
+
refreshed_key = await loop.run_in_executor(
|
|
2252
|
+
None, self._resolve_refreshed_api_key, e
|
|
2253
|
+
)
|
|
2254
|
+
if refreshed_key is not None:
|
|
2255
|
+
logger.warning(
|
|
2256
|
+
"Authentication error; refreshing API key and retrying once."
|
|
2257
|
+
)
|
|
2258
|
+
return await self._auth_refreshed_llm(refreshed_key).aresponses(
|
|
2259
|
+
messages,
|
|
2260
|
+
tools,
|
|
2261
|
+
include,
|
|
2262
|
+
store,
|
|
2263
|
+
add_security_risk_prediction=add_security_risk_prediction,
|
|
2264
|
+
on_token=on_token,
|
|
2265
|
+
call_context=call_context,
|
|
2266
|
+
**_caller_kwargs,
|
|
2267
|
+
)
|
|
2118
2268
|
_fb_token = cast("TokenCallbackType | None", on_token)
|
|
2119
2269
|
return await self._ahandle_error(
|
|
2120
2270
|
e,
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""JSON-file storage for meta-profiles.
|
|
2
|
+
|
|
3
|
+
Key invariant: every model reference in a meta-profile (``classifier_model``,
|
|
4
|
+
each class's ``model``, and direct-routing prompt outputs) is the *name of a
|
|
5
|
+
saved LLM profile* in
|
|
6
|
+
:class:`~openhands.sdk.llm.llm_profile_store.LLMProfileStore`, not a raw model
|
|
7
|
+
string — credentials/provider settings resolve through that store.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import tempfile
|
|
16
|
+
from collections.abc import Iterator
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Final
|
|
20
|
+
|
|
21
|
+
from filelock import FileLock, Timeout
|
|
22
|
+
from pydantic import BaseModel, Field, model_validator
|
|
23
|
+
|
|
24
|
+
from openhands.sdk.llm.llm_profile_store import PROFILE_NAME_REGEX
|
|
25
|
+
from openhands.sdk.logger import get_logger
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_LOCK_TIMEOUT_SECONDS: Final[float] = 30.0
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def default_meta_profile_dir() -> Path:
|
|
34
|
+
"""Return the default directory for meta-profile storage.
|
|
35
|
+
|
|
36
|
+
Agent-server instances can set ``OH_PERSISTENCE_DIR`` to isolate all
|
|
37
|
+
persisted state. Honor that for bare ``MetaProfileStore()`` instances so
|
|
38
|
+
runtime tools read the same meta-profiles exposed by the HTTP API. Without
|
|
39
|
+
that env var, keep the standalone SDK default under ``~/.openhands``.
|
|
40
|
+
"""
|
|
41
|
+
env_dir = os.environ.get("OH_PERSISTENCE_DIR")
|
|
42
|
+
if env_dir:
|
|
43
|
+
return Path(env_dir) / "meta-profiles"
|
|
44
|
+
return Path.home() / ".openhands" / "meta-profiles"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MetaProfileLimitExceeded(Exception):
|
|
48
|
+
"""Raised when saving would exceed the configured meta-profile limit."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MetaProfileClass(BaseModel):
|
|
52
|
+
"""A single task category and the LLM profile that should handle it."""
|
|
53
|
+
|
|
54
|
+
description: str = Field(
|
|
55
|
+
description="Natural-language description of the kind of task this "
|
|
56
|
+
"class covers (e.g. 'task is UI oriented or requires looking at images')."
|
|
57
|
+
)
|
|
58
|
+
model: str = Field(
|
|
59
|
+
description="Name of the saved LLM profile to switch to for tasks "
|
|
60
|
+
"matching this class."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MetaProfile(BaseModel):
|
|
65
|
+
"""A declarative model-routing configuration."""
|
|
66
|
+
|
|
67
|
+
classifier_model: str = Field(
|
|
68
|
+
description="Name of the saved LLM profile used to classify the task."
|
|
69
|
+
)
|
|
70
|
+
classes: list[MetaProfileClass] = Field(
|
|
71
|
+
default_factory=list,
|
|
72
|
+
description="Ordered list of task classes and their target profiles.",
|
|
73
|
+
)
|
|
74
|
+
prompt_template: str | None = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description=(
|
|
77
|
+
"Optional direct-routing prompt template. When set, the classifier "
|
|
78
|
+
"receives this rendered prompt and should return JSON with a "
|
|
79
|
+
"`model` field instead of a class number."
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
model_table: str | None = Field(
|
|
83
|
+
default=None,
|
|
84
|
+
description=(
|
|
85
|
+
"Optional text inserted into `{{ model_table }}` for direct-routing "
|
|
86
|
+
"prompts."
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@model_validator(mode="after")
|
|
91
|
+
def validate_routing_mode(self) -> MetaProfile:
|
|
92
|
+
"""Require either structured classes or a direct prompt, not both."""
|
|
93
|
+
if self.prompt_template is None:
|
|
94
|
+
return self
|
|
95
|
+
if re.search(r"{{\s*instance_text\s*}}", self.prompt_template) is None:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"Direct-routing meta-profiles must include {{ instance_text }} "
|
|
98
|
+
"in prompt_template."
|
|
99
|
+
)
|
|
100
|
+
if self.classes:
|
|
101
|
+
raise ValueError("Direct-routing meta-profiles cannot also define classes.")
|
|
102
|
+
return self
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class MetaProfileStore:
|
|
106
|
+
"""Read meta-profiles from the default meta-profile directory."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, base_dir: Path | str | None = None) -> None:
|
|
109
|
+
"""Initialize the meta-profile store.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
base_dir: Directory where meta-profiles are stored. Defaults to
|
|
113
|
+
``$OH_PERSISTENCE_DIR/meta-profiles`` when set, otherwise
|
|
114
|
+
``~/.openhands/meta-profiles``.
|
|
115
|
+
"""
|
|
116
|
+
self.base_dir = (
|
|
117
|
+
Path(base_dir) if base_dir is not None else default_meta_profile_dir()
|
|
118
|
+
)
|
|
119
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
self._file_lock = FileLock(self.base_dir / ".meta-profiles.lock")
|
|
121
|
+
|
|
122
|
+
@contextmanager
|
|
123
|
+
def _acquire_lock(self, timeout: float = _LOCK_TIMEOUT_SECONDS) -> Iterator[None]:
|
|
124
|
+
"""Acquire the file lock for safe concurrent access.
|
|
125
|
+
|
|
126
|
+
The lock is reentrant within a process, so methods that hold it may
|
|
127
|
+
call other locked methods (e.g. ``save`` calls ``list``).
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
TimeoutError: If the lock cannot be acquired within ``timeout``.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
with self._file_lock.acquire(timeout=timeout):
|
|
134
|
+
yield
|
|
135
|
+
except Timeout:
|
|
136
|
+
logger.error(
|
|
137
|
+
f"[Meta-profile Store] Failed to acquire lock within {timeout}s"
|
|
138
|
+
)
|
|
139
|
+
raise TimeoutError(
|
|
140
|
+
f"Meta-profile store lock acquisition timed out after {timeout}s"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def list(self) -> list[str]:
|
|
144
|
+
"""Return stored meta-profile names (without ``.json``), sorted.
|
|
145
|
+
|
|
146
|
+
The order is alphabetical, not chronological. Callers that fall back to
|
|
147
|
+
"the first available" meta-profile (when none is explicitly active) get
|
|
148
|
+
the alphabetically-first name, which is stable but not "most recent".
|
|
149
|
+
"""
|
|
150
|
+
return sorted(
|
|
151
|
+
p.stem
|
|
152
|
+
for p in self.base_dir.glob("*.json")
|
|
153
|
+
if PROFILE_NAME_REGEX.match(p.stem)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _get_path(self, name: str) -> Path:
|
|
157
|
+
clean_name = name.removesuffix(".json")
|
|
158
|
+
if not PROFILE_NAME_REGEX.match(clean_name):
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"Invalid meta-profile name: {name!r}. "
|
|
161
|
+
"Names must be 1-64 characters, start with a letter or digit, "
|
|
162
|
+
"and contain only letters, digits, '.', '_', or '-'."
|
|
163
|
+
)
|
|
164
|
+
return self.base_dir / f"{clean_name}.json"
|
|
165
|
+
|
|
166
|
+
def load(self, name: str) -> MetaProfile:
|
|
167
|
+
"""Load a meta-profile by name.
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
FileNotFoundError: If the meta-profile does not exist.
|
|
171
|
+
ValueError: If the file is corrupted or fails validation.
|
|
172
|
+
"""
|
|
173
|
+
path = self._get_path(name)
|
|
174
|
+
if not path.exists():
|
|
175
|
+
existing = self.list()
|
|
176
|
+
raise FileNotFoundError(
|
|
177
|
+
f"Meta-profile `{name}` not found. "
|
|
178
|
+
f"Available meta-profiles: {', '.join(existing) or 'none'}"
|
|
179
|
+
)
|
|
180
|
+
try:
|
|
181
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
182
|
+
return MetaProfile.model_validate(data)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
raise ValueError(f"Failed to load meta-profile `{name}`: {e}") from e
|
|
185
|
+
|
|
186
|
+
def save(
|
|
187
|
+
self,
|
|
188
|
+
name: str,
|
|
189
|
+
meta_profile: MetaProfile,
|
|
190
|
+
*,
|
|
191
|
+
max_profiles: int | None = None,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Persist a meta-profile under ``name`` (atomic write, overwrites).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
name: Name of the meta-profile to save.
|
|
197
|
+
meta_profile: The meta-profile to persist.
|
|
198
|
+
max_profiles: Optional cap on the number of meta-profiles. When set,
|
|
199
|
+
raises :class:`MetaProfileLimitExceeded` if creating a *new*
|
|
200
|
+
meta-profile would exceed the limit.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
MetaProfileLimitExceeded: If ``max_profiles`` would be exceeded.
|
|
204
|
+
ValueError: If ``name`` is not a valid meta-profile name.
|
|
205
|
+
"""
|
|
206
|
+
path = self._get_path(name)
|
|
207
|
+
|
|
208
|
+
# Hold the lock across the precondition check and the atomic replace so
|
|
209
|
+
# concurrent creators cannot both pass the limit check and overshoot
|
|
210
|
+
# ``max_profiles`` (TOCTOU), mirroring ``LLMProfileStore``.
|
|
211
|
+
with self._acquire_lock():
|
|
212
|
+
if max_profiles is not None and not path.exists():
|
|
213
|
+
if len(self.list()) >= max_profiles:
|
|
214
|
+
raise MetaProfileLimitExceeded(
|
|
215
|
+
f"Meta-profile limit reached ({max_profiles})."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
profile_json = json.dumps(meta_profile.model_dump(mode="json"), indent=2)
|
|
219
|
+
with tempfile.NamedTemporaryFile(
|
|
220
|
+
mode="w",
|
|
221
|
+
dir=self.base_dir,
|
|
222
|
+
suffix=".tmp",
|
|
223
|
+
delete=False,
|
|
224
|
+
encoding="utf-8",
|
|
225
|
+
) as tmp:
|
|
226
|
+
tmp.write(profile_json)
|
|
227
|
+
tmp_path = Path(tmp.name)
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
Path.replace(tmp_path, path)
|
|
231
|
+
except Exception:
|
|
232
|
+
tmp_path.unlink(missing_ok=True)
|
|
233
|
+
raise
|
|
234
|
+
logger.info(f"Saved meta-profile `{name}` at {path}")
|
|
235
|
+
|
|
236
|
+
def delete(self, name: str) -> None:
|
|
237
|
+
"""Delete a meta-profile (idempotent — missing names are a no-op).
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
ValueError: If ``name`` is not a valid meta-profile name.
|
|
241
|
+
"""
|
|
242
|
+
path = self._get_path(name)
|
|
243
|
+
with self._acquire_lock():
|
|
244
|
+
if not path.exists():
|
|
245
|
+
logger.info(f"Meta-profile `{name}` not found. Skipping delete.")
|
|
246
|
+
return
|
|
247
|
+
path.unlink()
|
|
248
|
+
logger.info(f"Deleted meta-profile `{name}`")
|
|
249
|
+
|
|
250
|
+
def list_summaries(self) -> list[dict[str, Any]]:
|
|
251
|
+
"""List meta-profile metadata without full schema validation.
|
|
252
|
+
|
|
253
|
+
Files with corrupted JSON or non-dict top-level values are skipped with
|
|
254
|
+
a warning so a single bad file never breaks the listing.
|
|
255
|
+
"""
|
|
256
|
+
summaries: list[dict[str, Any]] = []
|
|
257
|
+
for name in self.list():
|
|
258
|
+
try:
|
|
259
|
+
data = json.loads(self._get_path(name).read_text(encoding="utf-8"))
|
|
260
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
261
|
+
logger.warning(f"Skipping corrupted meta-profile {name!r}: {e}")
|
|
262
|
+
continue
|
|
263
|
+
if not isinstance(data, dict):
|
|
264
|
+
logger.warning(f"Skipping non-dict meta-profile {name!r}")
|
|
265
|
+
continue
|
|
266
|
+
classes = data.get("classes") or []
|
|
267
|
+
summaries.append(
|
|
268
|
+
{
|
|
269
|
+
"name": name,
|
|
270
|
+
"classifier_model": data.get("classifier_model"),
|
|
271
|
+
"num_classes": len(classes) if isinstance(classes, list) else 0,
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
return summaries
|
|
@@ -11,6 +11,8 @@ stay. When a new version lands, drop the oldest one in the same line.
|
|
|
11
11
|
|
|
12
12
|
# GPT: gpt-6 and gpt-5.6. Codex: gpt-5.3-codex and gpt-5.2-codex.
|
|
13
13
|
VERIFIED_OPENAI_MODELS = [
|
|
14
|
+
"gpt-6-sol",
|
|
15
|
+
"gpt-6-luna",
|
|
14
16
|
"gpt-6-astra",
|
|
15
17
|
"gpt-5.6",
|
|
16
18
|
"gpt-5.6-sol",
|
|
@@ -20,10 +22,10 @@ VERIFIED_OPENAI_MODELS = [
|
|
|
20
22
|
"gpt-5.2-codex",
|
|
21
23
|
]
|
|
22
24
|
|
|
23
|
-
# Opus: 5 and
|
|
25
|
+
# Opus: 5.5 and 5. Sonnet: 5 and 4.6. Haiku: 4.5. Fable: 5.1 and 5.
|
|
24
26
|
VERIFIED_ANTHROPIC_MODELS = [
|
|
27
|
+
"claude-opus-5-5",
|
|
25
28
|
"claude-opus-5",
|
|
26
|
-
"claude-opus-4-8",
|
|
27
29
|
"claude-sonnet-5",
|
|
28
30
|
"claude-sonnet-4-6",
|
|
29
31
|
"claude-haiku-4-5-20251001",
|
|
@@ -104,12 +106,14 @@ VERIFIED_QWEN_MODELS = [
|
|
|
104
106
|
# What the ``openhands/`` provider serves. Same rule; every entry must also be in
|
|
105
107
|
# a provider list above, except OpenHands-only models.
|
|
106
108
|
VERIFIED_OPENHANDS_MODELS = [
|
|
109
|
+
"claude-opus-5-5",
|
|
107
110
|
"claude-opus-5",
|
|
108
|
-
"claude-opus-4-8",
|
|
109
111
|
"claude-sonnet-5",
|
|
110
112
|
"claude-sonnet-4-6",
|
|
111
113
|
"claude-fable-5-1",
|
|
112
114
|
"claude-fable-5",
|
|
115
|
+
"gpt-6-sol",
|
|
116
|
+
"gpt-6-luna",
|
|
113
117
|
"gpt-6-astra",
|
|
114
118
|
"gpt-5.6",
|
|
115
119
|
"gpt-5.3-codex",
|