openhands-sdk 1.27.0__tar.gz → 1.28.0__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.27.0 → openhands_sdk-1.28.0}/PKG-INFO +1 -1
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/acp_agent.py +55 -9
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/agent.py +20 -3
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/utils.py +14 -14
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +15 -4
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/conversation.py +6 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/impl/local_conversation.py +73 -4
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/impl/remote_conversation.py +38 -1
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/request.py +11 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/exceptions/classifier.py +12 -2
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/llm.py +149 -26
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/llm_profile_store.py +4 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/model_features.py +35 -7
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/model_info.py +6 -0
- openhands_sdk-1.28.0/openhands/sdk/llm/utils/openhands_provider.py +87 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/telemetry.py +5 -5
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/settings/api_models.py +13 -2
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/settings/model.py +14 -1
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/__init__.py +12 -0
- openhands_sdk-1.28.0/openhands/sdk/tool/client_tool.py +404 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands_sdk.egg-info/SOURCES.txt +4 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/pyproject.toml +1 -1
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/security_policy.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/self_documentation.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/system_prompt.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/skills/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/serialization_diff.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/pydantic_diff.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.28.0
|
|
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
|
|
@@ -509,15 +509,19 @@ async def _maybe_set_session_model(
|
|
|
509
509
|
use the protocol call for initial selection (codex-acp, gemini-cli) get a
|
|
510
510
|
one-shot ``set_session_model`` call here.
|
|
511
511
|
|
|
512
|
+
For unknown/custom providers (e.g. Devin CLI), we fall back to the generic
|
|
513
|
+
``set_config_option`` method with configId="model", which is a standard ACP
|
|
514
|
+
method that many custom ACP servers support.
|
|
515
|
+
|
|
512
516
|
Runtime, mid-conversation switches go through
|
|
513
517
|
:meth:`ACPAgent.set_acp_model` instead, which always uses
|
|
514
518
|
``set_session_model`` and is gated on the separate
|
|
515
519
|
``supports_runtime_model_switch`` capability flag.
|
|
516
520
|
|
|
517
|
-
Returns ``True`` only when this issued a
|
|
521
|
+
Returns ``True`` only when this issued a model-setting call that succeeded — i.e.
|
|
518
522
|
the override was actually pushed to the server via *this* path. ``False``
|
|
519
523
|
when there is nothing to apply (no ``acp_model``) or the provider selects
|
|
520
|
-
its model another way (``_meta``) or
|
|
524
|
+
its model another way (``_meta``) or the server rejected the call, so
|
|
521
525
|
the caller can tell whether the live session is really running ``acp_model``.
|
|
522
526
|
"""
|
|
523
527
|
if not acp_model:
|
|
@@ -526,6 +530,29 @@ async def _maybe_set_session_model(
|
|
|
526
530
|
if provider is not None and provider.supports_set_session_model:
|
|
527
531
|
await conn.set_session_model(model_id=acp_model, session_id=session_id)
|
|
528
532
|
return True
|
|
533
|
+
# For unknown/custom providers, try the generic set_config_option method
|
|
534
|
+
# which is a standard ACP protocol method for setting configuration options
|
|
535
|
+
if provider is None:
|
|
536
|
+
try:
|
|
537
|
+
await conn.set_config_option(
|
|
538
|
+
config_id="model",
|
|
539
|
+
value=acp_model,
|
|
540
|
+
session_id=session_id,
|
|
541
|
+
)
|
|
542
|
+
logger.info(
|
|
543
|
+
"Set model %r on unknown/custom ACP server %s via set_config_option",
|
|
544
|
+
acp_model,
|
|
545
|
+
agent_name,
|
|
546
|
+
)
|
|
547
|
+
return True
|
|
548
|
+
except ACPRequestError as e:
|
|
549
|
+
logger.warning(
|
|
550
|
+
"Could not set model %r on unknown/custom ACP server %s via "
|
|
551
|
+
"set_config_option (%s); the session will use the server default",
|
|
552
|
+
acp_model,
|
|
553
|
+
agent_name,
|
|
554
|
+
e,
|
|
555
|
+
)
|
|
529
556
|
return False
|
|
530
557
|
|
|
531
558
|
|
|
@@ -542,6 +569,10 @@ async def _reapply_session_model_on_resume(
|
|
|
542
569
|
the ACP server's default. This issues ``set_session_model`` so the resumed
|
|
543
570
|
live session matches the serialized ``acp_model``.
|
|
544
571
|
|
|
572
|
+
For unknown/custom providers (e.g. Devin CLI), we fall back to the generic
|
|
573
|
+
``set_config_option`` method with configId="model", which is a standard ACP
|
|
574
|
+
method that many custom ACP servers support.
|
|
575
|
+
|
|
545
576
|
The gating mirrors :meth:`ACPAgent.set_acp_model` (attempt for custom/unknown
|
|
546
577
|
servers and known providers that support runtime switching; skip only known
|
|
547
578
|
providers that don't), deliberately differing from the initial-selection
|
|
@@ -550,7 +581,7 @@ async def _reapply_session_model_on_resume(
|
|
|
550
581
|
tolerated (logged) — like the ``load_session`` fallback above — so resume
|
|
551
582
|
can't break; the session keeps the server default until the next switch.
|
|
552
583
|
|
|
553
|
-
Returns ``True`` only when
|
|
584
|
+
Returns ``True`` only when a model-setting call was issued and accepted, so
|
|
554
585
|
the caller knows the resumed live session is actually running ``acp_model``.
|
|
555
586
|
``False`` when there is nothing to reapply, the provider doesn't support the
|
|
556
587
|
switch, or the server rejected the call (swallowed) — in those cases the
|
|
@@ -563,7 +594,22 @@ async def _reapply_session_model_on_resume(
|
|
|
563
594
|
if provider is not None and not provider.supports_runtime_model_switch:
|
|
564
595
|
return False
|
|
565
596
|
try:
|
|
566
|
-
|
|
597
|
+
if provider is not None:
|
|
598
|
+
# Known provider: use set_session_model
|
|
599
|
+
await conn.set_session_model(model_id=acp_model, session_id=session_id)
|
|
600
|
+
else:
|
|
601
|
+
# Unknown/custom provider: try set_config_option as fallback
|
|
602
|
+
await conn.set_config_option(
|
|
603
|
+
config_id="model",
|
|
604
|
+
value=acp_model,
|
|
605
|
+
session_id=session_id,
|
|
606
|
+
)
|
|
607
|
+
logger.info(
|
|
608
|
+
"Reapplied model %r on unknown/custom ACP server %s "
|
|
609
|
+
"via set_config_option",
|
|
610
|
+
acp_model,
|
|
611
|
+
agent_name,
|
|
612
|
+
)
|
|
567
613
|
return True
|
|
568
614
|
except ACPRequestError as e:
|
|
569
615
|
logger.warning(
|
|
@@ -1589,10 +1635,10 @@ class ACPAgent(AgentBase):
|
|
|
1589
1635
|
"""Whether a live, mid-conversation model switch will be attempted.
|
|
1590
1636
|
|
|
1591
1637
|
Tells a client whether to offer the inline picker's live-switch control.
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1638
|
+
``True`` only for known providers that explicitly declare support for
|
|
1639
|
+
``session/set_model``. Unknown/custom providers use ``set_config_option``
|
|
1640
|
+
for *initial* model selection but that RPC is a generic config write, not
|
|
1641
|
+
a guaranteed live-switch primitive, so the picker is hidden for them.
|
|
1596
1642
|
``False`` before a session exists (nothing to switch yet).
|
|
1597
1643
|
|
|
1598
1644
|
See
|
|
@@ -1601,7 +1647,7 @@ class ACPAgent(AgentBase):
|
|
|
1601
1647
|
if self._session_id is None:
|
|
1602
1648
|
return False
|
|
1603
1649
|
provider = detect_acp_provider_by_agent_name(self._agent_name)
|
|
1604
|
-
return provider is None
|
|
1650
|
+
return provider is not None and provider.supports_runtime_model_switch
|
|
1605
1651
|
|
|
1606
1652
|
def get_all_llms(self) -> Generator[LLM]:
|
|
1607
1653
|
yield self.llm
|
|
@@ -584,9 +584,10 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
584
584
|
"skipping hook check for legacy conversation state."
|
|
585
585
|
)
|
|
586
586
|
|
|
587
|
-
# Prepare LLM messages
|
|
587
|
+
# Prepare LLM messages from the cached, incrementally-maintained view.
|
|
588
|
+
# See https://github.com/OpenHands/software-agent-sdk/issues/3053.
|
|
588
589
|
_messages_or_condensation = prepare_llm_messages(
|
|
589
|
-
state.
|
|
590
|
+
state.view, condenser=self.condenser, llm=self.llm
|
|
590
591
|
)
|
|
591
592
|
|
|
592
593
|
# Process condensation event before agent sampels another action
|
|
@@ -724,8 +725,10 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
724
725
|
"skipping hook check for legacy conversation state."
|
|
725
726
|
)
|
|
726
727
|
|
|
728
|
+
# Prepare LLM messages from the cached, incrementally-maintained view.
|
|
729
|
+
# See https://github.com/OpenHands/software-agent-sdk/issues/3053.
|
|
727
730
|
_messages_or_condensation = await aprepare_llm_messages(
|
|
728
|
-
state.
|
|
731
|
+
state.view, condenser=self.condenser, llm=self.llm
|
|
729
732
|
)
|
|
730
733
|
|
|
731
734
|
if isinstance(_messages_or_condensation, Condensation):
|
|
@@ -955,6 +958,20 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
955
958
|
thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
|
|
956
959
|
responses_reasoning_item: ReasoningItemModel | None = None,
|
|
957
960
|
) -> None:
|
|
961
|
+
try:
|
|
962
|
+
json.loads(tool_call.arguments)
|
|
963
|
+
except json.JSONDecodeError:
|
|
964
|
+
tool_call = tool_call.model_copy(
|
|
965
|
+
update={
|
|
966
|
+
"arguments": json.dumps(
|
|
967
|
+
{
|
|
968
|
+
"_openhands_malformed_tool_call": True,
|
|
969
|
+
"error": error,
|
|
970
|
+
}
|
|
971
|
+
)
|
|
972
|
+
}
|
|
973
|
+
)
|
|
974
|
+
|
|
958
975
|
tc_event = ActionEvent(
|
|
959
976
|
source="agent",
|
|
960
977
|
thought=thought or [],
|
|
@@ -10,7 +10,7 @@ import shutil
|
|
|
10
10
|
import subprocess
|
|
11
11
|
import textwrap
|
|
12
12
|
import types
|
|
13
|
-
from collections.abc import Collection
|
|
13
|
+
from collections.abc import Collection
|
|
14
14
|
from typing import (
|
|
15
15
|
TYPE_CHECKING,
|
|
16
16
|
Annotated,
|
|
@@ -24,7 +24,7 @@ from typing import (
|
|
|
24
24
|
from openhands.sdk.context.condenser.base import CondenserBase
|
|
25
25
|
from openhands.sdk.context.view import View
|
|
26
26
|
from openhands.sdk.conversation.types import ConversationTokenCallbackType
|
|
27
|
-
from openhands.sdk.event.base import
|
|
27
|
+
from openhands.sdk.event.base import LLMConvertibleEvent
|
|
28
28
|
from openhands.sdk.event.condenser import Condensation
|
|
29
29
|
from openhands.sdk.llm import LLM, LLMResponse, Message
|
|
30
30
|
from openhands.sdk.tool import Action, ToolDefinition
|
|
@@ -499,7 +499,7 @@ def normalize_tool_call(
|
|
|
499
499
|
|
|
500
500
|
@overload
|
|
501
501
|
def prepare_llm_messages(
|
|
502
|
-
|
|
502
|
+
view: View,
|
|
503
503
|
condenser: None = None,
|
|
504
504
|
additional_messages: list[Message] | None = None,
|
|
505
505
|
llm: LLM | None = None,
|
|
@@ -508,7 +508,7 @@ def prepare_llm_messages(
|
|
|
508
508
|
|
|
509
509
|
@overload
|
|
510
510
|
def prepare_llm_messages(
|
|
511
|
-
|
|
511
|
+
view: View,
|
|
512
512
|
condenser: CondenserBase,
|
|
513
513
|
additional_messages: list[Message] | None = None,
|
|
514
514
|
llm: LLM | None = None,
|
|
@@ -516,19 +516,25 @@ def prepare_llm_messages(
|
|
|
516
516
|
|
|
517
517
|
|
|
518
518
|
def prepare_llm_messages(
|
|
519
|
-
|
|
519
|
+
view: View,
|
|
520
520
|
condenser: CondenserBase | None = None,
|
|
521
521
|
additional_messages: list[Message] | None = None,
|
|
522
522
|
llm: LLM | None = None,
|
|
523
523
|
) -> list[Message] | Condensation:
|
|
524
|
-
"""Prepare LLM messages from conversation
|
|
524
|
+
"""Prepare LLM messages from a conversation view.
|
|
525
525
|
|
|
526
526
|
This utility function extracts the common logic for preparing conversation
|
|
527
527
|
context that is shared between agent.step() and ask_agent() methods.
|
|
528
528
|
It handles condensation internally and calls the callback when needed.
|
|
529
529
|
|
|
530
|
+
Callers should pass the cached `ConversationState.view`, which is
|
|
531
|
+
maintained incrementally as events are appended. This avoids paying the
|
|
532
|
+
O(n) `View.from_events` (with `enforce_properties`) cost on every step.
|
|
533
|
+
See https://github.com/OpenHands/software-agent-sdk/issues/3053.
|
|
534
|
+
|
|
530
535
|
Args:
|
|
531
|
-
|
|
536
|
+
view: A `View` of the conversation history. The view is treated as
|
|
537
|
+
read-only — see `CondenserBase.condense` for the same contract.
|
|
532
538
|
condenser: Optional condenser for handling context window limits
|
|
533
539
|
additional_messages: Optional additional messages to append
|
|
534
540
|
llm: Optional LLM instance from the agent, passed to condenser for
|
|
@@ -537,12 +543,7 @@ def prepare_llm_messages(
|
|
|
537
543
|
Returns:
|
|
538
544
|
List of messages ready for LLM completion, or a Condensation event
|
|
539
545
|
if condensation is needed
|
|
540
|
-
|
|
541
|
-
Raises:
|
|
542
|
-
RuntimeError: If condensation is needed but no callback is provided
|
|
543
546
|
"""
|
|
544
|
-
|
|
545
|
-
view = View.from_events(events)
|
|
546
547
|
llm_convertible_events: list[LLMConvertibleEvent] = view.events
|
|
547
548
|
|
|
548
549
|
# If a condenser is registered, we need to give it an
|
|
@@ -622,7 +623,7 @@ def make_llm_completion(
|
|
|
622
623
|
|
|
623
624
|
|
|
624
625
|
async def aprepare_llm_messages(
|
|
625
|
-
|
|
626
|
+
view: View,
|
|
626
627
|
condenser: CondenserBase | None = None,
|
|
627
628
|
additional_messages: list[Message] | None = None,
|
|
628
629
|
llm: LLM | None = None,
|
|
@@ -632,7 +633,6 @@ async def aprepare_llm_messages(
|
|
|
632
633
|
Calls ``condenser.acondense()`` so that condensers backed by an LLM can
|
|
633
634
|
use async completions without blocking the event loop.
|
|
634
635
|
"""
|
|
635
|
-
view = View.from_events(events)
|
|
636
636
|
llm_convertible_events: list[LLMConvertibleEvent] = view.events
|
|
637
637
|
|
|
638
638
|
if condenser is not None:
|
|
@@ -177,9 +177,15 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
177
177
|
|
|
178
178
|
# Do not pass extra_body explicitly. The LLM handles forwarding
|
|
179
179
|
# litellm_extra_body only when it is non-empty.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
try:
|
|
181
|
+
llm_response = self.llm.completion(
|
|
182
|
+
messages=messages,
|
|
183
|
+
)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
raise NoCondensationAvailableException(
|
|
186
|
+
f"Summarization LLM call failed: {e}"
|
|
187
|
+
) from e
|
|
188
|
+
|
|
183
189
|
# Extract summary from the LLMResponse message
|
|
184
190
|
summary = None
|
|
185
191
|
if llm_response.message.content:
|
|
@@ -364,7 +370,12 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
364
370
|
)
|
|
365
371
|
|
|
366
372
|
messages = [Message(role="user", content=[TextContent(text=prompt)])]
|
|
367
|
-
|
|
373
|
+
try:
|
|
374
|
+
llm_response = await self.llm.acompletion(messages=messages)
|
|
375
|
+
except Exception as e:
|
|
376
|
+
raise NoCondensationAvailableException(
|
|
377
|
+
f"Summarization LLM call failed: {e}"
|
|
378
|
+
) from e
|
|
368
379
|
|
|
369
380
|
summary = None
|
|
370
381
|
if llm_response.message.content:
|
|
@@ -18,6 +18,7 @@ from openhands.sdk.hooks import HookConfig
|
|
|
18
18
|
from openhands.sdk.logger import get_logger
|
|
19
19
|
from openhands.sdk.plugin import PluginSource
|
|
20
20
|
from openhands.sdk.secret import SecretValue
|
|
21
|
+
from openhands.sdk.tool.client_tool import ClientToolSpec
|
|
21
22
|
from openhands.sdk.workspace import LocalWorkspace, RemoteWorkspace
|
|
22
23
|
|
|
23
24
|
|
|
@@ -81,6 +82,7 @@ class Conversation:
|
|
|
81
82
|
delete_on_close: bool = True,
|
|
82
83
|
tags: dict[str, str] | None = None,
|
|
83
84
|
user_id: str | None = None,
|
|
85
|
+
client_tools: list[ClientToolSpec] | None = None,
|
|
84
86
|
) -> "LocalConversation": ...
|
|
85
87
|
|
|
86
88
|
@overload
|
|
@@ -106,6 +108,7 @@ class Conversation:
|
|
|
106
108
|
delete_on_close: bool = True,
|
|
107
109
|
tags: dict[str, str] | None = None,
|
|
108
110
|
user_id: str | None = None,
|
|
111
|
+
client_tools: list[ClientToolSpec] | None = None,
|
|
109
112
|
) -> "RemoteConversation": ...
|
|
110
113
|
|
|
111
114
|
def __new__(
|
|
@@ -131,6 +134,7 @@ class Conversation:
|
|
|
131
134
|
delete_on_close: bool = True,
|
|
132
135
|
tags: dict[str, str] | None = None,
|
|
133
136
|
user_id: str | None = None,
|
|
137
|
+
client_tools: list[ClientToolSpec] | None = None,
|
|
134
138
|
) -> BaseConversation:
|
|
135
139
|
from openhands.sdk.conversation.impl.local_conversation import LocalConversation
|
|
136
140
|
from openhands.sdk.conversation.impl.remote_conversation import (
|
|
@@ -185,6 +189,7 @@ class Conversation:
|
|
|
185
189
|
delete_on_close=delete_on_close,
|
|
186
190
|
tags=effective_tags if effective_tags else None,
|
|
187
191
|
user_id=user_id,
|
|
192
|
+
client_tools=client_tools,
|
|
188
193
|
)
|
|
189
194
|
|
|
190
195
|
return LocalConversation(
|
|
@@ -204,4 +209,5 @@ class Conversation:
|
|
|
204
209
|
delete_on_close=delete_on_close,
|
|
205
210
|
tags=tags,
|
|
206
211
|
user_id=user_id,
|
|
212
|
+
client_tools=client_tools,
|
|
207
213
|
)
|
{openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/impl/local_conversation.py
RENAMED
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import atexit
|
|
3
3
|
import contextlib
|
|
4
4
|
import copy
|
|
5
|
+
import json
|
|
5
6
|
import uuid
|
|
6
7
|
from collections.abc import Mapping
|
|
7
8
|
from pathlib import Path
|
|
@@ -68,6 +69,7 @@ from openhands.sdk.subagent import (
|
|
|
68
69
|
register_file_agents,
|
|
69
70
|
register_plugin_agents,
|
|
70
71
|
)
|
|
72
|
+
from openhands.sdk.tool.client_tool import ClientToolSpec
|
|
71
73
|
from openhands.sdk.tool.schema import Action, Observation
|
|
72
74
|
from openhands.sdk.utils.cipher import Cipher
|
|
73
75
|
from openhands.sdk.workspace import LocalWorkspace
|
|
@@ -148,6 +150,7 @@ class LocalConversation(BaseConversation):
|
|
|
148
150
|
cipher: Cipher | None = None,
|
|
149
151
|
tags: dict[str, str] | None = None,
|
|
150
152
|
user_id: str | None = None,
|
|
153
|
+
client_tools: list[ClientToolSpec] | None = None,
|
|
151
154
|
**_: object,
|
|
152
155
|
):
|
|
153
156
|
"""Initialize the conversation.
|
|
@@ -189,6 +192,11 @@ class LocalConversation(BaseConversation):
|
|
|
189
192
|
(lost) on serialization.
|
|
190
193
|
tags: Optional key-value tags for the conversation. Keys must be
|
|
191
194
|
lowercase alphanumeric, values up to 256 characters.
|
|
195
|
+
client_tools: Optional list of client-defined tool specs. Each spec
|
|
196
|
+
is registered and injected into the agent so it can call the
|
|
197
|
+
tool; the executor returns an acknowledgment and the real
|
|
198
|
+
execution is expected to be handled by a callback/consumer
|
|
199
|
+
(e.g. a frontend) observing the emitted ActionEvent.
|
|
192
200
|
"""
|
|
193
201
|
super().__init__() # Initialize with span tracking
|
|
194
202
|
# Mark cleanup as initiated as early as possible to avoid races or partially
|
|
@@ -206,6 +214,32 @@ class LocalConversation(BaseConversation):
|
|
|
206
214
|
self._pending_hook_config = hook_config # Will be combined with plugin hooks
|
|
207
215
|
self._agent_ready = False # Agent initialized lazily after plugins loaded
|
|
208
216
|
|
|
217
|
+
# Create-or-resume: factory inspects BASE_STATE to decide
|
|
218
|
+
desired_id = conversation_id or uuid.uuid4()
|
|
219
|
+
|
|
220
|
+
# Resolve client-defined tools, then register them and inject the matching
|
|
221
|
+
# Tool specs into the agent so the agent can call them. Execution is
|
|
222
|
+
# deferred to a consumer of the emitted ActionEvent (e.g. a frontend); the
|
|
223
|
+
# executor only acks. Specs come either from the caller (`client_tools`)
|
|
224
|
+
# or, when resuming a persisted conversation without re-supplying them,
|
|
225
|
+
# from the persisted agent's tool specs — mirroring the server resume
|
|
226
|
+
# path so a fresh process can re-register the dynamic tools.
|
|
227
|
+
resolved_client_tools = list(client_tools or [])
|
|
228
|
+
if not resolved_client_tools and persistence_dir is not None:
|
|
229
|
+
resolved_client_tools = self._recover_persisted_client_tools(
|
|
230
|
+
persistence_dir, desired_id
|
|
231
|
+
)
|
|
232
|
+
if resolved_client_tools:
|
|
233
|
+
from openhands.sdk.tool.client_tool import register_client_tools
|
|
234
|
+
|
|
235
|
+
client_tool_specs = register_client_tools(resolved_client_tools)
|
|
236
|
+
existing_names = {t.name for t in agent.tools}
|
|
237
|
+
new_tools = [
|
|
238
|
+
ts for ts in client_tool_specs if ts.name not in existing_names
|
|
239
|
+
]
|
|
240
|
+
if new_tools:
|
|
241
|
+
agent = agent.model_copy(update={"tools": [*agent.tools, *new_tools]})
|
|
242
|
+
|
|
209
243
|
self.agent = agent
|
|
210
244
|
if isinstance(workspace, (str, Path)):
|
|
211
245
|
# LocalWorkspace accepts both str and Path via BeforeValidator
|
|
@@ -217,9 +251,6 @@ class LocalConversation(BaseConversation):
|
|
|
217
251
|
ws_path = Path(self.workspace.working_dir)
|
|
218
252
|
if not ws_path.exists():
|
|
219
253
|
ws_path.mkdir(parents=True, exist_ok=True)
|
|
220
|
-
|
|
221
|
-
# Create-or-resume: factory inspects BASE_STATE to decide
|
|
222
|
-
desired_id = conversation_id or uuid.uuid4()
|
|
223
254
|
self._state = ConversationState.create(
|
|
224
255
|
id=desired_id,
|
|
225
256
|
agent=agent,
|
|
@@ -344,6 +375,44 @@ class LocalConversation(BaseConversation):
|
|
|
344
375
|
self._start_observability_span(str(desired_id), user_id=user_id)
|
|
345
376
|
self.delete_on_close = delete_on_close
|
|
346
377
|
|
|
378
|
+
def _recover_persisted_client_tools(
|
|
379
|
+
self,
|
|
380
|
+
persistence_base_dir: str | Path,
|
|
381
|
+
conversation_id: ConversationID,
|
|
382
|
+
) -> list[ClientToolSpec]:
|
|
383
|
+
"""Recover client tool specs from a persisted conversation's base state.
|
|
384
|
+
|
|
385
|
+
When a persisted conversation is resumed in a fresh process, the dynamic
|
|
386
|
+
client tools are absent from the global registry and the caller may not
|
|
387
|
+
re-supply ``client_tools``. Without recovery, the persisted agent's
|
|
388
|
+
client tools would appear "removed" and resume would fail. We read the
|
|
389
|
+
persisted agent tool specs and pull out the embedded ``ClientToolSpec``s
|
|
390
|
+
so they can be re-registered and re-injected. Returns an empty list when
|
|
391
|
+
there is no persisted state yet (fresh conversation).
|
|
392
|
+
"""
|
|
393
|
+
from pydantic import ValidationError
|
|
394
|
+
|
|
395
|
+
from openhands.sdk.conversation.persistence_const import BASE_STATE
|
|
396
|
+
from openhands.sdk.tool.client_tool import extract_client_tool_specs
|
|
397
|
+
from openhands.sdk.tool.spec import Tool
|
|
398
|
+
|
|
399
|
+
base_path = (
|
|
400
|
+
Path(self.get_persistence_dir(persistence_base_dir, conversation_id))
|
|
401
|
+
/ BASE_STATE
|
|
402
|
+
)
|
|
403
|
+
try:
|
|
404
|
+
data = json.loads(base_path.read_text())
|
|
405
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
406
|
+
return []
|
|
407
|
+
raw_tools = (data.get("agent") or {}).get("tools") or []
|
|
408
|
+
tools: list[Tool] = []
|
|
409
|
+
for raw_tool in raw_tools:
|
|
410
|
+
try:
|
|
411
|
+
tools.append(Tool.model_validate(raw_tool))
|
|
412
|
+
except ValidationError:
|
|
413
|
+
continue
|
|
414
|
+
return extract_client_tool_specs(tools)
|
|
415
|
+
|
|
347
416
|
@property
|
|
348
417
|
def id(self) -> ConversationID:
|
|
349
418
|
"""Get the unique ID of the conversation."""
|
|
@@ -1798,7 +1867,7 @@ class LocalConversation(BaseConversation):
|
|
|
1798
1867
|
)
|
|
1799
1868
|
|
|
1800
1869
|
messages = prepare_llm_messages(
|
|
1801
|
-
self.state.
|
|
1870
|
+
self.state.view, additional_messages=[user_message]
|
|
1802
1871
|
)
|
|
1803
1872
|
|
|
1804
1873
|
# Get or create the specialized ask-agent LLM
|
{openhands_sdk-1.27.0 → openhands_sdk-1.28.0}/openhands/sdk/conversation/impl/remote_conversation.py
RENAMED
|
@@ -53,6 +53,7 @@ from openhands.sdk.security.analyzer import SecurityAnalyzerBase
|
|
|
53
53
|
from openhands.sdk.security.confirmation_policy import (
|
|
54
54
|
ConfirmationPolicyBase,
|
|
55
55
|
)
|
|
56
|
+
from openhands.sdk.tool.client_tool import ClientTool, ClientToolSpec
|
|
56
57
|
from openhands.sdk.utils.redact import http_error_log_content
|
|
57
58
|
from openhands.sdk.workspace import LocalWorkspace, RemoteWorkspace
|
|
58
59
|
|
|
@@ -671,6 +672,7 @@ class RemoteConversation(BaseConversation):
|
|
|
671
672
|
delete_on_close: bool = False,
|
|
672
673
|
tags: dict[str, str] | None = None,
|
|
673
674
|
user_id: str | None = None,
|
|
675
|
+
client_tools: list[ClientToolSpec] | None = None,
|
|
674
676
|
**_: object,
|
|
675
677
|
) -> None:
|
|
676
678
|
"""Remote conversation proxy that talks to an agent server.
|
|
@@ -700,6 +702,10 @@ class RemoteConversation(BaseConversation):
|
|
|
700
702
|
tags: Optional key-value tags for the conversation. Keys must be
|
|
701
703
|
lowercase alphanumeric, values up to 256 characters.
|
|
702
704
|
user_id: Optional user ID to associate with observability traces
|
|
705
|
+
client_tools: Optional list of client-defined tool specs. These tools
|
|
706
|
+
have no server-side executor — when the agent calls them an
|
|
707
|
+
ActionEvent is emitted over the WebSocket and the client
|
|
708
|
+
handles execution via callbacks.
|
|
703
709
|
"""
|
|
704
710
|
super().__init__() # Initialize base class with span tracking
|
|
705
711
|
self.agent = agent
|
|
@@ -713,6 +719,12 @@ class RemoteConversation(BaseConversation):
|
|
|
713
719
|
self._terminal_status_queue: Queue[str] = Queue()
|
|
714
720
|
self._run_armed = threading.Event()
|
|
715
721
|
|
|
722
|
+
# Client tool specs the server already has persisted for this
|
|
723
|
+
# conversation (populated when re-attaching to an existing one). These
|
|
724
|
+
# must be registered locally before the initial event sync so that
|
|
725
|
+
# persisted ``ClientAction_*`` events can be deserialized.
|
|
726
|
+
attached_client_tools: list[ClientToolSpec] = []
|
|
727
|
+
|
|
716
728
|
should_create = conversation_id is None
|
|
717
729
|
if conversation_id is not None:
|
|
718
730
|
# Try to attach to existing conversation
|
|
@@ -726,11 +738,18 @@ class RemoteConversation(BaseConversation):
|
|
|
726
738
|
# Conversation doesn't exist, we'll create it
|
|
727
739
|
should_create = True
|
|
728
740
|
else:
|
|
729
|
-
|
|
741
|
+
info = resp.json()
|
|
742
|
+
agent_payload = info.get("agent")
|
|
730
743
|
if agent_payload is not None:
|
|
731
744
|
remote_agent = _validate_remote_agent(agent_payload)
|
|
732
745
|
if remote_agent.agent_kind != agent.agent_kind:
|
|
733
746
|
raise ValueError(_agent_kind_mismatch_message(conversation_id))
|
|
747
|
+
# Capture persisted client tool specs so we can register their
|
|
748
|
+
# dynamic action types before RemoteState syncs events.
|
|
749
|
+
for raw_spec in info.get("client_tools") or []:
|
|
750
|
+
attached_client_tools.append(
|
|
751
|
+
ClientToolSpec.model_validate(raw_spec)
|
|
752
|
+
)
|
|
734
753
|
# Conversation exists, use the provided ID
|
|
735
754
|
self._id = conversation_id
|
|
736
755
|
|
|
@@ -765,6 +784,12 @@ class RemoteConversation(BaseConversation):
|
|
|
765
784
|
"plugins": [p.model_dump() for p in plugins] if plugins else None,
|
|
766
785
|
# Include hook_config for server-side hooks
|
|
767
786
|
"hook_config": hook_config.model_dump() if hook_config else None,
|
|
787
|
+
# Include client-defined tool specs (no server-side executor)
|
|
788
|
+
"client_tools": (
|
|
789
|
+
[s.model_dump(mode="json") for s in client_tools]
|
|
790
|
+
if client_tools
|
|
791
|
+
else []
|
|
792
|
+
),
|
|
768
793
|
# Include tags if provided
|
|
769
794
|
"tags": tags or {},
|
|
770
795
|
}
|
|
@@ -797,6 +822,18 @@ class RemoteConversation(BaseConversation):
|
|
|
797
822
|
|
|
798
823
|
workspace.register_conversation(str(self._id))
|
|
799
824
|
|
|
825
|
+
# Register client tool action types locally so WebSocket/persisted
|
|
826
|
+
# events with ClientAction_* action_type can be deserialized by the
|
|
827
|
+
# event loop. This must cover both the specs the caller passed in and
|
|
828
|
+
# the specs the server already had persisted (when re-attaching), so a
|
|
829
|
+
# plain reattach by conversation_id can still sync persisted events.
|
|
830
|
+
seen_client_tool_names: set[str] = set()
|
|
831
|
+
for spec in [*(client_tools or []), *attached_client_tools]:
|
|
832
|
+
if spec.name in seen_client_tool_names:
|
|
833
|
+
continue
|
|
834
|
+
seen_client_tool_names.add(spec.name)
|
|
835
|
+
ClientTool.from_spec(spec)
|
|
836
|
+
|
|
800
837
|
# Initialize the remote state
|
|
801
838
|
self._state = RemoteState(
|
|
802
839
|
self._client,
|
|
@@ -27,6 +27,7 @@ from openhands.sdk.security.confirmation_policy import (
|
|
|
27
27
|
NeverConfirm,
|
|
28
28
|
)
|
|
29
29
|
from openhands.sdk.subagent.schema import AgentDefinition
|
|
30
|
+
from openhands.sdk.tool.client_tool import ClientToolSpec
|
|
30
31
|
from openhands.sdk.utils.models import kind_of
|
|
31
32
|
from openhands.sdk.workspace import LocalWorkspace
|
|
32
33
|
|
|
@@ -138,6 +139,16 @@ class StartConversationRequest(BaseModel):
|
|
|
138
139
|
"to register the tools for this conversation."
|
|
139
140
|
),
|
|
140
141
|
)
|
|
142
|
+
client_tools: list[ClientToolSpec] = Field(
|
|
143
|
+
default_factory=list,
|
|
144
|
+
description=(
|
|
145
|
+
"Tools defined by the client via JSON spec. These tools have "
|
|
146
|
+
"no server-side executor — when the agent calls them, an "
|
|
147
|
+
"ActionEvent is emitted over the WebSocket and the client "
|
|
148
|
+
"handles execution. The SDK returns an acknowledgment "
|
|
149
|
+
"observation immediately."
|
|
150
|
+
),
|
|
151
|
+
)
|
|
141
152
|
agent_definitions: list[AgentDefinition] = Field(
|
|
142
153
|
default_factory=list,
|
|
143
154
|
description=(
|
|
@@ -5,6 +5,7 @@ from litellm.exceptions import (
|
|
|
5
5
|
AuthenticationError,
|
|
6
6
|
BadRequestError,
|
|
7
7
|
ContextWindowExceededError,
|
|
8
|
+
InternalServerError,
|
|
8
9
|
OpenAIError,
|
|
9
10
|
PermissionDeniedError,
|
|
10
11
|
)
|
|
@@ -48,6 +49,9 @@ MALFORMED_HISTORY_PATTERNS: list[str] = [
|
|
|
48
49
|
),
|
|
49
50
|
# Moonshot / Kimi variant
|
|
50
51
|
"must be followed by tool messages responding to each 'tool_call_id'",
|
|
52
|
+
# OpenAI-compatible providers may reject replayed assistant tool calls whose
|
|
53
|
+
# arguments are not valid JSON.
|
|
54
|
+
"failed to parse tool call arguments as json",
|
|
51
55
|
]
|
|
52
56
|
|
|
53
57
|
|
|
@@ -58,7 +62,10 @@ def is_context_window_exceeded(exception: Exception) -> bool:
|
|
|
58
62
|
# Check for litellm/openai exception types that may contain context window errors.
|
|
59
63
|
# APIConnectionError can wrap provider-specific errors (e.g., Minimax) that include
|
|
60
64
|
# context window messages in their error text.
|
|
61
|
-
if not isinstance(
|
|
65
|
+
if not isinstance(
|
|
66
|
+
exception,
|
|
67
|
+
(BadRequestError, OpenAIError, APIConnectionError, InternalServerError),
|
|
68
|
+
):
|
|
62
69
|
return False
|
|
63
70
|
|
|
64
71
|
s = str(exception).lower()
|
|
@@ -69,7 +76,10 @@ def looks_like_malformed_conversation_history_error(exception: Exception) -> boo
|
|
|
69
76
|
if isinstance(exception, LLMMalformedConversationHistoryError):
|
|
70
77
|
return True
|
|
71
78
|
|
|
72
|
-
if not isinstance(
|
|
79
|
+
if not isinstance(
|
|
80
|
+
exception,
|
|
81
|
+
(BadRequestError, OpenAIError, APIConnectionError, InternalServerError),
|
|
82
|
+
):
|
|
73
83
|
return False
|
|
74
84
|
|
|
75
85
|
s = str(exception).lower()
|