openhands-sdk 1.8.2__tar.gz → 1.9.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.8.2 → openhands_sdk-1.9.0}/PKG-INFO +5 -1
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/agent.py +64 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/base.py +22 -10
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/skills/skill.py +59 -1
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/skills/utils.py +6 -65
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/base.py +5 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/impl/remote_conversation.py +16 -3
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/visualizer/base.py +23 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/critic/__init__.py +4 -1
- openhands_sdk-1.9.0/openhands/sdk/critic/base.py +35 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/critic/impl/__init__.py +2 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/critic/impl/agent_finished.py +9 -5
- openhands_sdk-1.9.0/openhands/sdk/critic/impl/api/__init__.py +18 -0
- openhands_sdk-1.9.0/openhands/sdk/critic/impl/api/chat_template.py +232 -0
- openhands_sdk-1.9.0/openhands/sdk/critic/impl/api/client.py +313 -0
- openhands_sdk-1.9.0/openhands/sdk/critic/impl/api/critic.py +90 -0
- openhands_sdk-1.9.0/openhands/sdk/critic/impl/api/taxonomy.py +180 -0
- openhands_sdk-1.9.0/openhands/sdk/critic/result.py +148 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/llm_convertible/action.py +10 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/llm_convertible/message.py +10 -0
- openhands_sdk-1.9.0/openhands/sdk/git/cached_repo.py +459 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/git/utils.py +118 -3
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/hooks/__init__.py +7 -1
- openhands_sdk-1.9.0/openhands/sdk/hooks/config.py +289 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/model_features.py +3 -0
- openhands_sdk-1.9.0/openhands/sdk/plugin/__init__.py +39 -0
- openhands_sdk-1.9.0/openhands/sdk/plugin/fetch.py +231 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/plugin/plugin.py +61 -4
- openhands_sdk-1.9.0/openhands/sdk/plugin/types.py +619 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands_sdk.egg-info/PKG-INFO +5 -1
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands_sdk.egg-info/SOURCES.txt +16 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/pyproject.toml +7 -1
- openhands_sdk-1.8.2/openhands/sdk/critic/base.py +0 -38
- openhands_sdk-1.8.2/openhands/sdk/hooks/config.py +0 -180
- openhands_sdk-1.8.2/openhands/sdk/plugin/__init__.py +0 -22
- openhands_sdk-1.8.2/openhands/sdk/plugin/types.py +0 -226
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/security_policy.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/self_documentation.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/system_prompt.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/skills/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/skills/exceptions.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/skills/trigger.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/skills/types.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/context/view.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/impl/local_conversation.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/serialization_diff.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/llm.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/pydantic_diff.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/setup.cfg +0 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
|
+
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
|
+
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|
|
7
|
+
Project-URL: Documentation, https://docs.openhands.dev/sdk
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/OpenHands/software-agent-sdk/issues
|
|
5
9
|
Requires-Python: >=3.12
|
|
6
10
|
Requires-Dist: deprecation>=2.1.0
|
|
7
11
|
Requires-Dist: fastmcp>=2.11.3
|
|
@@ -17,6 +17,7 @@ from openhands.sdk.conversation import (
|
|
|
17
17
|
LocalConversation,
|
|
18
18
|
)
|
|
19
19
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
20
|
+
from openhands.sdk.critic.base import CriticResult
|
|
20
21
|
from openhands.sdk.event import (
|
|
21
22
|
ActionEvent,
|
|
22
23
|
AgentErrorEvent,
|
|
@@ -120,6 +121,48 @@ class Agent(AgentBase):
|
|
|
120
121
|
)
|
|
121
122
|
on_event(event)
|
|
122
123
|
|
|
124
|
+
def _should_evaluate_with_critic(self, action: Action | None) -> bool:
|
|
125
|
+
"""Determine if critic should evaluate based on action type and mode."""
|
|
126
|
+
if self.critic is None:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
if self.critic.mode == "all_actions":
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
# For "finish_and_message" mode, only evaluate FinishAction
|
|
133
|
+
# (MessageEvent will be handled separately in step())
|
|
134
|
+
if isinstance(action, FinishAction):
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
def _evaluate_with_critic(
|
|
140
|
+
self, conversation: LocalConversation, event: ActionEvent | MessageEvent
|
|
141
|
+
) -> CriticResult | None:
|
|
142
|
+
"""Run critic evaluation on the current event and history."""
|
|
143
|
+
if self.critic is None:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# Build event history including the current event
|
|
148
|
+
events = list(conversation.state.events) + [event]
|
|
149
|
+
llm_convertible_events = [
|
|
150
|
+
e for e in events if isinstance(e, LLMConvertibleEvent)
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
# Evaluate without git_patch for now
|
|
154
|
+
critic_result = self.critic.evaluate(
|
|
155
|
+
events=llm_convertible_events, git_patch=None
|
|
156
|
+
)
|
|
157
|
+
logger.info(
|
|
158
|
+
f"✓ Critic evaluation: score={critic_result.score:.3f}, "
|
|
159
|
+
f"success={critic_result.success}"
|
|
160
|
+
)
|
|
161
|
+
return critic_result
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"✗ Critic evaluation failed: {e}", exc_info=True)
|
|
164
|
+
return None
|
|
165
|
+
|
|
123
166
|
def _execute_actions(
|
|
124
167
|
self,
|
|
125
168
|
conversation: LocalConversation,
|
|
@@ -237,6 +280,7 @@ class Agent(AgentBase):
|
|
|
237
280
|
for i, tool_call in enumerate(message.tool_calls):
|
|
238
281
|
action_event = self._get_action_event(
|
|
239
282
|
tool_call,
|
|
283
|
+
conversation=conversation,
|
|
240
284
|
llm_response_id=llm_response.id,
|
|
241
285
|
on_event=on_event,
|
|
242
286
|
security_analyzer=state.security_analyzer,
|
|
@@ -275,6 +319,14 @@ class Agent(AgentBase):
|
|
|
275
319
|
llm_message=message,
|
|
276
320
|
llm_response_id=llm_response.id,
|
|
277
321
|
)
|
|
322
|
+
# Run critic evaluation if configured for finish_and_message mode
|
|
323
|
+
if self.critic is not None and self.critic.mode == "finish_and_message":
|
|
324
|
+
critic_result = self._evaluate_with_critic(conversation, msg_event)
|
|
325
|
+
if critic_result is not None:
|
|
326
|
+
# Create new event with critic result
|
|
327
|
+
msg_event = msg_event.model_copy(
|
|
328
|
+
update={"critic_result": critic_result}
|
|
329
|
+
)
|
|
278
330
|
on_event(msg_event)
|
|
279
331
|
|
|
280
332
|
# Emit VLLM token ids if enabled
|
|
@@ -389,6 +441,7 @@ class Agent(AgentBase):
|
|
|
389
441
|
def _get_action_event(
|
|
390
442
|
self,
|
|
391
443
|
tool_call: MessageToolCall,
|
|
444
|
+
conversation: LocalConversation,
|
|
392
445
|
llm_response_id: str,
|
|
393
446
|
on_event: ConversationCallbackType,
|
|
394
447
|
security_analyzer: analyzer.SecurityAnalyzerBase | None = None,
|
|
@@ -477,6 +530,7 @@ class Agent(AgentBase):
|
|
|
477
530
|
on_event(event)
|
|
478
531
|
return
|
|
479
532
|
|
|
533
|
+
# Create initial action event
|
|
480
534
|
action_event = ActionEvent(
|
|
481
535
|
action=action,
|
|
482
536
|
thought=thought or [],
|
|
@@ -490,6 +544,16 @@ class Agent(AgentBase):
|
|
|
490
544
|
security_risk=security_risk,
|
|
491
545
|
summary=summary,
|
|
492
546
|
)
|
|
547
|
+
|
|
548
|
+
# Run critic evaluation if configured
|
|
549
|
+
if self._should_evaluate_with_critic(action):
|
|
550
|
+
critic_result = self._evaluate_with_critic(conversation, action_event)
|
|
551
|
+
if critic_result is not None:
|
|
552
|
+
# Create new event with critic result
|
|
553
|
+
action_event = action_event.model_copy(
|
|
554
|
+
update={"critic_result": critic_result}
|
|
555
|
+
)
|
|
556
|
+
|
|
493
557
|
on_event(action_event)
|
|
494
558
|
return action_event
|
|
495
559
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
import re
|
|
3
5
|
import sys
|
|
@@ -16,6 +18,7 @@ from pydantic import (
|
|
|
16
18
|
from openhands.sdk.context.agent_context import AgentContext
|
|
17
19
|
from openhands.sdk.context.condenser import CondenserBase
|
|
18
20
|
from openhands.sdk.context.prompts.prompt import render_template
|
|
21
|
+
from openhands.sdk.critic.base import CriticBase
|
|
19
22
|
from openhands.sdk.llm import LLM
|
|
20
23
|
from openhands.sdk.llm.utils.model_prompt_spec import get_model_prompt_spec
|
|
21
24
|
from openhands.sdk.logger import get_logger
|
|
@@ -37,7 +40,6 @@ if TYPE_CHECKING:
|
|
|
37
40
|
ConversationTokenCallbackType,
|
|
38
41
|
)
|
|
39
42
|
|
|
40
|
-
|
|
41
43
|
logger = get_logger(__name__)
|
|
42
44
|
|
|
43
45
|
|
|
@@ -174,6 +176,16 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
174
176
|
],
|
|
175
177
|
)
|
|
176
178
|
|
|
179
|
+
critic: CriticBase | None = Field(
|
|
180
|
+
default=None,
|
|
181
|
+
description=(
|
|
182
|
+
"EXPERIMENTAL: Optional critic to evaluate agent actions and messages "
|
|
183
|
+
"in real-time. API and behavior may change without notice. "
|
|
184
|
+
"May impact performance, especially in 'all_actions' mode."
|
|
185
|
+
),
|
|
186
|
+
examples=[{"kind": "AgentFinishedCritic"}],
|
|
187
|
+
)
|
|
188
|
+
|
|
177
189
|
# Runtime materialized tools; private and non-serializable
|
|
178
190
|
_tools: dict[str, ToolDefinition] = PrivateAttr(default_factory=dict)
|
|
179
191
|
_initialized: bool = PrivateAttr(default=False)
|
|
@@ -226,8 +238,8 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
226
238
|
|
|
227
239
|
def init_state(
|
|
228
240
|
self,
|
|
229
|
-
state:
|
|
230
|
-
on_event:
|
|
241
|
+
state: ConversationState,
|
|
242
|
+
on_event: ConversationCallbackType, # noqa: ARG002
|
|
231
243
|
) -> None:
|
|
232
244
|
"""Initialize the empty conversation state to prepare the agent for user
|
|
233
245
|
messages.
|
|
@@ -238,7 +250,7 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
238
250
|
"""
|
|
239
251
|
self._initialize(state)
|
|
240
252
|
|
|
241
|
-
def _initialize(self, state:
|
|
253
|
+
def _initialize(self, state: ConversationState):
|
|
242
254
|
"""Create an AgentBase instance from an AgentSpec."""
|
|
243
255
|
|
|
244
256
|
if self._initialized:
|
|
@@ -310,9 +322,9 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
310
322
|
@abstractmethod
|
|
311
323
|
def step(
|
|
312
324
|
self,
|
|
313
|
-
conversation:
|
|
314
|
-
on_event:
|
|
315
|
-
on_token:
|
|
325
|
+
conversation: LocalConversation,
|
|
326
|
+
on_event: ConversationCallbackType,
|
|
327
|
+
on_token: ConversationTokenCallbackType | None = None,
|
|
316
328
|
) -> None:
|
|
317
329
|
"""Taking a step in the conversation.
|
|
318
330
|
|
|
@@ -332,9 +344,9 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
332
344
|
|
|
333
345
|
def verify(
|
|
334
346
|
self,
|
|
335
|
-
persisted:
|
|
336
|
-
events:
|
|
337
|
-
) ->
|
|
347
|
+
persisted: AgentBase,
|
|
348
|
+
events: Sequence[Any] | None = None,
|
|
349
|
+
) -> AgentBase:
|
|
338
350
|
"""Verify that we can resume this agent from persisted state.
|
|
339
351
|
|
|
340
352
|
This PR's goal is to *not* reconcile configuration between persisted and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import re
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Annotated, ClassVar, Union
|
|
4
|
+
from typing import Annotated, ClassVar, Literal, Union
|
|
5
5
|
from xml.sax.saxutils import escape as xml_escape
|
|
6
6
|
|
|
7
7
|
import frontmatter
|
|
@@ -37,6 +37,22 @@ logger = get_logger(__name__)
|
|
|
37
37
|
THIRD_PARTY_SKILL_MAX_CHARS = 10_000
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
class SkillInfo(BaseModel):
|
|
41
|
+
"""Lightweight representation of a skill's essential information.
|
|
42
|
+
|
|
43
|
+
This class provides a standardized, serializable format for skill metadata
|
|
44
|
+
that can be used across different components of the system.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
name: str
|
|
48
|
+
type: Literal["repo", "knowledge", "agentskills"]
|
|
49
|
+
content: str
|
|
50
|
+
triggers: list[str] = Field(default_factory=list)
|
|
51
|
+
source: str | None = None
|
|
52
|
+
description: str | None = None
|
|
53
|
+
is_agentskills_format: bool = False
|
|
54
|
+
|
|
55
|
+
|
|
40
56
|
class SkillResources(BaseModel):
|
|
41
57
|
"""Resource directories for a skill (AgentSkills standard).
|
|
42
58
|
|
|
@@ -560,6 +576,48 @@ class Skill(BaseModel):
|
|
|
560
576
|
logger.debug(f"This skill requires user input: {variables}")
|
|
561
577
|
return len(variables) > 0
|
|
562
578
|
|
|
579
|
+
def get_skill_type(self) -> Literal["repo", "knowledge", "agentskills"]:
|
|
580
|
+
"""Determine the type of this skill.
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
"agentskills" for AgentSkills format, "repo" for always-active skills,
|
|
584
|
+
"knowledge" for trigger-based skills.
|
|
585
|
+
"""
|
|
586
|
+
if self.is_agentskills_format:
|
|
587
|
+
return "agentskills"
|
|
588
|
+
elif self.trigger is None:
|
|
589
|
+
return "repo"
|
|
590
|
+
else:
|
|
591
|
+
return "knowledge"
|
|
592
|
+
|
|
593
|
+
def get_triggers(self) -> list[str]:
|
|
594
|
+
"""Extract trigger keywords from this skill.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
List of trigger strings, or empty list if no triggers.
|
|
598
|
+
"""
|
|
599
|
+
if isinstance(self.trigger, KeywordTrigger):
|
|
600
|
+
return self.trigger.keywords
|
|
601
|
+
elif isinstance(self.trigger, TaskTrigger):
|
|
602
|
+
return self.trigger.triggers
|
|
603
|
+
return []
|
|
604
|
+
|
|
605
|
+
def to_skill_info(self) -> SkillInfo:
|
|
606
|
+
"""Convert this skill to a SkillInfo.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
SkillInfo containing the skill's essential information.
|
|
610
|
+
"""
|
|
611
|
+
return SkillInfo(
|
|
612
|
+
name=self.name,
|
|
613
|
+
type=self.get_skill_type(),
|
|
614
|
+
content=self.content,
|
|
615
|
+
triggers=self.get_triggers(),
|
|
616
|
+
source=self.source,
|
|
617
|
+
description=self.description,
|
|
618
|
+
is_agentskills_format=self.is_agentskills_format,
|
|
619
|
+
)
|
|
620
|
+
|
|
563
621
|
|
|
564
622
|
def load_skills_from_dir(
|
|
565
623
|
skill_dir: str | Path,
|
|
@@ -5,14 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
|
-
import shutil
|
|
9
|
-
import subprocess
|
|
10
8
|
from pathlib import Path
|
|
11
9
|
from typing import TYPE_CHECKING
|
|
12
10
|
|
|
13
11
|
from fastmcp.mcp_config import MCPConfig
|
|
14
12
|
|
|
15
13
|
from openhands.sdk.context.skills.exceptions import SkillValidationError
|
|
14
|
+
from openhands.sdk.git.cached_repo import try_cached_clone_or_update
|
|
16
15
|
from openhands.sdk.logger import get_logger
|
|
17
16
|
|
|
18
17
|
|
|
@@ -316,77 +315,19 @@ def update_skills_repository(
|
|
|
316
315
|
) -> Path | None:
|
|
317
316
|
"""Clone or update the local skills repository.
|
|
318
317
|
|
|
318
|
+
Uses the shared git caching infrastructure from openhands.sdk.git.cached_repo.
|
|
319
|
+
When updating, performs: fetch -> checkout ref -> reset --hard to origin/ref.
|
|
320
|
+
|
|
319
321
|
Args:
|
|
320
322
|
repo_url: URL of the skills repository.
|
|
321
|
-
branch: Branch name to
|
|
323
|
+
branch: Branch name to checkout and track.
|
|
322
324
|
cache_dir: Directory where the repository should be cached.
|
|
323
325
|
|
|
324
326
|
Returns:
|
|
325
327
|
Path to the local repository if successful, None otherwise.
|
|
326
328
|
"""
|
|
327
329
|
repo_path = cache_dir / "public-skills"
|
|
328
|
-
|
|
329
|
-
try:
|
|
330
|
-
if repo_path.exists() and (repo_path / ".git").exists():
|
|
331
|
-
logger.debug(f"Updating skills repository at {repo_path}")
|
|
332
|
-
try:
|
|
333
|
-
subprocess.run(
|
|
334
|
-
["git", "fetch", "origin"],
|
|
335
|
-
cwd=repo_path,
|
|
336
|
-
check=True,
|
|
337
|
-
capture_output=True,
|
|
338
|
-
timeout=30,
|
|
339
|
-
)
|
|
340
|
-
subprocess.run(
|
|
341
|
-
["git", "reset", "--hard", f"origin/{branch}"],
|
|
342
|
-
cwd=repo_path,
|
|
343
|
-
check=True,
|
|
344
|
-
capture_output=True,
|
|
345
|
-
timeout=10,
|
|
346
|
-
)
|
|
347
|
-
logger.debug("Skills repository updated successfully")
|
|
348
|
-
except subprocess.TimeoutExpired:
|
|
349
|
-
logger.warning("Git pull timed out, using existing cached repository")
|
|
350
|
-
except subprocess.CalledProcessError as e:
|
|
351
|
-
logger.warning(
|
|
352
|
-
f"Failed to update repository: {e.stderr.decode()}, "
|
|
353
|
-
f"using existing cached version"
|
|
354
|
-
)
|
|
355
|
-
else:
|
|
356
|
-
logger.info(f"Cloning public skills repository from {repo_url}")
|
|
357
|
-
if repo_path.exists():
|
|
358
|
-
shutil.rmtree(repo_path)
|
|
359
|
-
|
|
360
|
-
subprocess.run(
|
|
361
|
-
[
|
|
362
|
-
"git",
|
|
363
|
-
"clone",
|
|
364
|
-
"--depth",
|
|
365
|
-
"1",
|
|
366
|
-
"--branch",
|
|
367
|
-
branch,
|
|
368
|
-
repo_url,
|
|
369
|
-
str(repo_path),
|
|
370
|
-
],
|
|
371
|
-
check=True,
|
|
372
|
-
capture_output=True,
|
|
373
|
-
timeout=60,
|
|
374
|
-
)
|
|
375
|
-
logger.debug(f"Skills repository cloned to {repo_path}")
|
|
376
|
-
|
|
377
|
-
return repo_path
|
|
378
|
-
|
|
379
|
-
except subprocess.TimeoutExpired:
|
|
380
|
-
logger.warning(f"Git operation timed out for {repo_url}")
|
|
381
|
-
return None
|
|
382
|
-
except subprocess.CalledProcessError as e:
|
|
383
|
-
logger.warning(
|
|
384
|
-
f"Failed to clone/update repository {repo_url}: {e.stderr.decode()}"
|
|
385
|
-
)
|
|
386
|
-
return None
|
|
387
|
-
except Exception as e:
|
|
388
|
-
logger.warning(f"Error managing skills repository: {str(e)}")
|
|
389
|
-
return None
|
|
330
|
+
return try_cached_clone_or_update(repo_url, repo_path, ref=branch, update=True)
|
|
390
331
|
|
|
391
332
|
|
|
392
333
|
def discover_skill_resources(skill_dir: Path) -> SkillResources:
|
|
@@ -162,6 +162,11 @@ class BaseConversation(ABC):
|
|
|
162
162
|
"""Set the confirmation policy for the conversation."""
|
|
163
163
|
...
|
|
164
164
|
|
|
165
|
+
@abstractmethod
|
|
166
|
+
def set_security_analyzer(self, analyzer: SecurityAnalyzerBase | None) -> None:
|
|
167
|
+
"""Set the security analyzer for the conversation."""
|
|
168
|
+
...
|
|
169
|
+
|
|
165
170
|
@property
|
|
166
171
|
def confirmation_policy_active(self) -> bool:
|
|
167
172
|
return not isinstance(self.state.confirmation_policy, NeverConfirm)
|
{openhands_sdk-1.8.2 → openhands_sdk-1.9.0}/openhands/sdk/conversation/impl/remote_conversation.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import bisect
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
4
5
|
import threading
|
|
@@ -219,13 +220,25 @@ class RemoteEventsList(EventsListBase):
|
|
|
219
220
|
logger.debug(f"Full sync completed, {len(events)} events cached")
|
|
220
221
|
|
|
221
222
|
def add_event(self, event: Event) -> None:
|
|
222
|
-
"""Add a new event to the local cache (called by WebSocket callback).
|
|
223
|
+
"""Add a new event to the local cache (called by WebSocket callback).
|
|
224
|
+
|
|
225
|
+
Events are inserted in sorted order by timestamp to maintain correct
|
|
226
|
+
temporal ordering regardless of WebSocket delivery order.
|
|
227
|
+
"""
|
|
223
228
|
with self._lock:
|
|
224
229
|
# Check if event already exists to avoid duplicates
|
|
225
230
|
if event.id not in self._cached_event_ids:
|
|
226
|
-
|
|
231
|
+
# Use bisect with key function for O(log N) insertion
|
|
232
|
+
# This ensures events are always ordered correctly even if
|
|
233
|
+
# WebSocket delivers them out of order
|
|
234
|
+
insert_pos = bisect.bisect_right(
|
|
235
|
+
self._cached_events, event.timestamp, key=lambda e: e.timestamp
|
|
236
|
+
)
|
|
237
|
+
self._cached_events.insert(insert_pos, event)
|
|
227
238
|
self._cached_event_ids.add(event.id)
|
|
228
|
-
logger.debug(
|
|
239
|
+
logger.debug(
|
|
240
|
+
f"Added event {event.id} to local cache at position {insert_pos}"
|
|
241
|
+
)
|
|
229
242
|
|
|
230
243
|
def append(self, event: Event) -> None:
|
|
231
244
|
"""Add a new event to the list (for compatibility with EventLog interface)."""
|
|
@@ -65,3 +65,26 @@ class ConversationVisualizerBase(ABC):
|
|
|
65
65
|
event: The event to visualize
|
|
66
66
|
"""
|
|
67
67
|
pass
|
|
68
|
+
|
|
69
|
+
def create_sub_visualizer(
|
|
70
|
+
self,
|
|
71
|
+
agent_id: str, # noqa: ARG002
|
|
72
|
+
) -> "ConversationVisualizerBase | None":
|
|
73
|
+
"""Create a visualizer for a sub-agent during delegation.
|
|
74
|
+
|
|
75
|
+
Override this method to support sub-agent visualization in multi-agent
|
|
76
|
+
delegation scenarios. The sub-visualizer will be used to display events
|
|
77
|
+
from the spawned sub-agent.
|
|
78
|
+
|
|
79
|
+
By default, returns None which means sub-agents will not have visualization.
|
|
80
|
+
Subclasses that support delegation (like DelegationVisualizer) should
|
|
81
|
+
override this method to create appropriate sub-visualizers.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
agent_id: The identifier of the sub-agent being spawned
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
A visualizer instance for the sub-agent, or None if sub-agent
|
|
88
|
+
visualization is not supported
|
|
89
|
+
"""
|
|
90
|
+
return None
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
from openhands.sdk.critic.base import CriticBase
|
|
1
|
+
from openhands.sdk.critic.base import CriticBase
|
|
2
2
|
from openhands.sdk.critic.impl import (
|
|
3
3
|
AgentFinishedCritic,
|
|
4
|
+
APIBasedCritic,
|
|
4
5
|
EmptyPatchCritic,
|
|
5
6
|
PassCritic,
|
|
6
7
|
)
|
|
8
|
+
from openhands.sdk.critic.result import CriticResult
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
__all__ = [
|
|
10
12
|
"CriticBase",
|
|
11
13
|
"CriticResult",
|
|
12
14
|
"AgentFinishedCritic",
|
|
15
|
+
"APIBasedCritic",
|
|
13
16
|
"EmptyPatchCritic",
|
|
14
17
|
"PassCritic",
|
|
15
18
|
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from typing import TYPE_CHECKING, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from openhands.sdk.critic.result import CriticResult
|
|
8
|
+
from openhands.sdk.utils.models import DiscriminatedUnionMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from openhands.sdk.event.base import LLMConvertibleEvent
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CriticBase(DiscriminatedUnionMixin, abc.ABC):
|
|
16
|
+
"""A critic is a function that takes in a list of events,
|
|
17
|
+
optional git patch, and returns a score about the quality of agent's action.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
mode: Literal["finish_and_message", "all_actions"] = Field(
|
|
21
|
+
default="finish_and_message",
|
|
22
|
+
description=(
|
|
23
|
+
"When to run critic evaluation:\n"
|
|
24
|
+
"- 'finish_and_message': Evaluate on FinishAction and agent"
|
|
25
|
+
" MessageEvent (default, minimal performance impact)\n"
|
|
26
|
+
"- 'all_actions': Evaluate after every agent action (WARNING: "
|
|
27
|
+
"significantly slower due to API calls on each action)"
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@abc.abstractmethod
|
|
32
|
+
def evaluate(
|
|
33
|
+
self, events: Sequence["LLMConvertibleEvent"], git_patch: str | None = None
|
|
34
|
+
) -> CriticResult:
|
|
35
|
+
pass
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Critic implementations module."""
|
|
2
2
|
|
|
3
3
|
from openhands.sdk.critic.impl.agent_finished import AgentFinishedCritic
|
|
4
|
+
from openhands.sdk.critic.impl.api import APIBasedCritic
|
|
4
5
|
from openhands.sdk.critic.impl.empty_patch import EmptyPatchCritic
|
|
5
6
|
from openhands.sdk.critic.impl.pass_critic import PassCritic
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"AgentFinishedCritic",
|
|
11
|
+
"APIBasedCritic",
|
|
10
12
|
"EmptyPatchCritic",
|
|
11
13
|
"PassCritic",
|
|
12
14
|
]
|
|
@@ -7,13 +7,17 @@ This critic evaluates whether an agent properly finished a task by checking:
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from collections.abc import Sequence
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
11
12
|
from openhands.sdk.critic.base import CriticBase, CriticResult
|
|
12
|
-
from openhands.sdk.event import ActionEvent, LLMConvertibleEvent
|
|
13
13
|
from openhands.sdk.logger import get_logger
|
|
14
14
|
from openhands.sdk.tool.builtins.finish import FinishAction
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from openhands.sdk.event.base import LLMConvertibleEvent
|
|
19
|
+
|
|
20
|
+
|
|
17
21
|
logger = get_logger(__name__)
|
|
18
22
|
|
|
19
23
|
|
|
@@ -27,7 +31,7 @@ class AgentFinishedCritic(CriticBase):
|
|
|
27
31
|
"""
|
|
28
32
|
|
|
29
33
|
def evaluate(
|
|
30
|
-
self, events: Sequence[LLMConvertibleEvent], git_patch: str | None = None
|
|
34
|
+
self, events: Sequence["LLMConvertibleEvent"], git_patch: str | None = None
|
|
31
35
|
) -> CriticResult:
|
|
32
36
|
"""
|
|
33
37
|
Evaluate if an agent properly finished with a non-empty git patch.
|
|
@@ -66,18 +70,18 @@ class AgentFinishedCritic(CriticBase):
|
|
|
66
70
|
message="Agent completed with FinishAction and non-empty patch",
|
|
67
71
|
)
|
|
68
72
|
|
|
69
|
-
def _has_finish_action(self, events: Sequence[LLMConvertibleEvent]) -> bool:
|
|
73
|
+
def _has_finish_action(self, events: Sequence["LLMConvertibleEvent"]) -> bool:
|
|
70
74
|
"""Check if the last action was a FinishAction."""
|
|
71
75
|
if not events:
|
|
72
76
|
return False
|
|
73
77
|
|
|
74
78
|
# Look for the last ActionEvent in the history
|
|
79
|
+
from openhands.sdk.event.llm_convertible.action import ActionEvent
|
|
80
|
+
|
|
75
81
|
for event in reversed(events):
|
|
76
82
|
if isinstance(event, ActionEvent):
|
|
77
|
-
# Check if this is a FinishAction
|
|
78
83
|
if event.action and isinstance(event.action, FinishAction):
|
|
79
84
|
return True
|
|
80
|
-
# If we find any other action type, the agent didn't finish
|
|
81
85
|
return False
|
|
82
86
|
|
|
83
87
|
return False
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from openhands.sdk.critic.impl.api.client import (
|
|
2
|
+
ClassificationItem,
|
|
3
|
+
ClassificationResponse,
|
|
4
|
+
CriticClient,
|
|
5
|
+
LabelProbMap,
|
|
6
|
+
UsageTokens,
|
|
7
|
+
)
|
|
8
|
+
from openhands.sdk.critic.impl.api.critic import APIBasedCritic
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"APIBasedCritic",
|
|
13
|
+
"CriticClient",
|
|
14
|
+
"ClassificationItem",
|
|
15
|
+
"ClassificationResponse",
|
|
16
|
+
"LabelProbMap",
|
|
17
|
+
"UsageTokens",
|
|
18
|
+
]
|