openhands-sdk 1.7.3__tar.gz → 1.8.1__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.7.3 → openhands_sdk-1.8.1}/PKG-INFO +2 -2
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/__init__.py +2 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/agent.py +31 -1
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/base.py +111 -67
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/system_prompt.j2 +1 -1
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/utils.py +3 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/agent_context.py +45 -3
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/condenser/__init__.py +2 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/condenser/base.py +59 -8
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +38 -10
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +4 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +9 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/skills/__init__.py +12 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/skills/skill.py +425 -228
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/skills/types.py +4 -0
- openhands_sdk-1.8.1/openhands/sdk/context/skills/utils.py +442 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/view.py +2 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/impl/local_conversation.py +42 -14
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/impl/remote_conversation.py +99 -55
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/state.py +54 -18
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/llm_convertible/action.py +20 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/git/utils.py +31 -6
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/hooks/conversation_hooks.py +57 -10
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/llm.py +59 -76
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/options/chat_options.py +4 -1
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/router/base.py +12 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/telemetry.py +2 -2
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/verified_models.py +1 -1
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/mcp/tool.py +3 -1
- openhands_sdk-1.8.1/openhands/sdk/plugin/__init__.py +22 -0
- openhands_sdk-1.8.1/openhands/sdk/plugin/plugin.py +299 -0
- openhands_sdk-1.8.1/openhands/sdk/plugin/types.py +226 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/__init__.py +7 -1
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/builtins/__init__.py +4 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/schema.py +6 -3
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/tool.py +60 -9
- openhands_sdk-1.8.1/openhands/sdk/utils/models.py +296 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/base.py +22 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/local.py +16 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/remote/async_remote_workspace.py +16 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/remote/base.py +16 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands_sdk.egg-info/PKG-INFO +2 -2
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands_sdk.egg-info/SOURCES.txt +8 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands_sdk.egg-info/requires.txt +1 -1
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/pyproject.toml +2 -2
- openhands_sdk-1.7.3/openhands/sdk/utils/models.py +0 -570
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/security_policy.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/self_documentation.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/skills/exceptions.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/context/skills/trigger.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/serialization_diff.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/pydantic_diff.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.7.3 → openhands_sdk-1.8.1}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.1
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: deprecation>=2.1.0
|
|
7
7
|
Requires-Dist: fastmcp>=2.11.3
|
|
8
8
|
Requires-Dist: httpx>=0.27.0
|
|
9
9
|
Requires-Dist: litellm>=1.80.10
|
|
10
|
-
Requires-Dist: pydantic>=2.
|
|
10
|
+
Requires-Dist: pydantic>=2.12.5
|
|
11
11
|
Requires-Dist: python-frontmatter>=1.1.0
|
|
12
12
|
Requires-Dist: python-json-logger>=3.3.0
|
|
13
13
|
Requires-Dist: tenacity>=9.1.2
|
|
@@ -41,6 +41,7 @@ from openhands.sdk.mcp import (
|
|
|
41
41
|
MCPToolObservation,
|
|
42
42
|
create_mcp_tools,
|
|
43
43
|
)
|
|
44
|
+
from openhands.sdk.plugin import Plugin
|
|
44
45
|
from openhands.sdk.tool import (
|
|
45
46
|
Action,
|
|
46
47
|
Observation,
|
|
@@ -98,6 +99,7 @@ __all__ = [
|
|
|
98
99
|
"LLMSummarizingCondenser",
|
|
99
100
|
"FileStore",
|
|
100
101
|
"LocalFileStore",
|
|
102
|
+
"Plugin",
|
|
101
103
|
"register_tool",
|
|
102
104
|
"resolve_tool",
|
|
103
105
|
"list_registered_tools",
|
|
@@ -27,7 +27,10 @@ from openhands.sdk.event import (
|
|
|
27
27
|
TokenEvent,
|
|
28
28
|
UserRejectObservation,
|
|
29
29
|
)
|
|
30
|
-
from openhands.sdk.event.condenser import
|
|
30
|
+
from openhands.sdk.event.condenser import (
|
|
31
|
+
Condensation,
|
|
32
|
+
CondensationRequest,
|
|
33
|
+
)
|
|
31
34
|
from openhands.sdk.llm import (
|
|
32
35
|
LLMResponse,
|
|
33
36
|
Message,
|
|
@@ -359,6 +362,30 @@ class Agent(AgentBase):
|
|
|
359
362
|
security_risk = risk.SecurityRisk(raw)
|
|
360
363
|
return security_risk
|
|
361
364
|
|
|
365
|
+
def _extract_summary(self, tool_name: str, arguments: dict) -> str:
|
|
366
|
+
"""Extract and validate the summary field from tool arguments.
|
|
367
|
+
|
|
368
|
+
Summary field is always requested but optional - if LLM doesn't provide
|
|
369
|
+
it or provides invalid data, we generate a default summary using the
|
|
370
|
+
tool name and arguments.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
tool_name: Name of the tool being called
|
|
374
|
+
arguments: Dictionary of tool arguments from LLM
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
The summary string - either from LLM or a default generated one
|
|
378
|
+
"""
|
|
379
|
+
summary = arguments.pop("summary", None)
|
|
380
|
+
|
|
381
|
+
# If valid summary provided by LLM, use it
|
|
382
|
+
if summary is not None and isinstance(summary, str) and summary.strip():
|
|
383
|
+
return summary
|
|
384
|
+
|
|
385
|
+
# Generate default summary: {tool_name}: {arguments}
|
|
386
|
+
args_str = json.dumps(arguments)
|
|
387
|
+
return f"{tool_name}: {args_str}"
|
|
388
|
+
|
|
362
389
|
def _get_action_event(
|
|
363
390
|
self,
|
|
364
391
|
tool_call: MessageToolCall,
|
|
@@ -420,6 +447,8 @@ class Agent(AgentBase):
|
|
|
420
447
|
"Unexpected 'security_risk' key found in tool arguments"
|
|
421
448
|
)
|
|
422
449
|
|
|
450
|
+
summary = self._extract_summary(tool.name, arguments)
|
|
451
|
+
|
|
423
452
|
action: Action = tool.action_from_arguments(arguments)
|
|
424
453
|
except (json.JSONDecodeError, ValidationError, ValueError) as e:
|
|
425
454
|
err = (
|
|
@@ -459,6 +488,7 @@ class Agent(AgentBase):
|
|
|
459
488
|
tool_call=tool_call,
|
|
460
489
|
llm_response_id=llm_response_id,
|
|
461
490
|
security_risk=security_risk,
|
|
491
|
+
summary=summary,
|
|
462
492
|
)
|
|
463
493
|
on_event(action_event)
|
|
464
494
|
return action_event
|
|
@@ -2,22 +2,32 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import sys
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
|
-
from collections.abc import Generator, Iterable
|
|
5
|
+
from collections.abc import Generator, Iterable, Sequence
|
|
6
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
7
7
|
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
|
-
from pydantic import
|
|
9
|
+
from pydantic import (
|
|
10
|
+
BaseModel,
|
|
11
|
+
ConfigDict,
|
|
12
|
+
Field,
|
|
13
|
+
PrivateAttr,
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
from openhands.sdk.context.agent_context import AgentContext
|
|
12
|
-
from openhands.sdk.context.condenser import CondenserBase
|
|
17
|
+
from openhands.sdk.context.condenser import CondenserBase
|
|
13
18
|
from openhands.sdk.context.prompts.prompt import render_template
|
|
14
19
|
from openhands.sdk.llm import LLM
|
|
15
20
|
from openhands.sdk.llm.utils.model_prompt_spec import get_model_prompt_spec
|
|
16
21
|
from openhands.sdk.logger import get_logger
|
|
17
22
|
from openhands.sdk.mcp import create_mcp_tools
|
|
18
|
-
from openhands.sdk.tool import
|
|
23
|
+
from openhands.sdk.tool import (
|
|
24
|
+
BUILT_IN_TOOL_CLASSES,
|
|
25
|
+
BUILT_IN_TOOLS,
|
|
26
|
+
Tool,
|
|
27
|
+
ToolDefinition,
|
|
28
|
+
resolve_tool,
|
|
29
|
+
)
|
|
19
30
|
from openhands.sdk.utils.models import DiscriminatedUnionMixin
|
|
20
|
-
from openhands.sdk.utils.pydantic_diff import pretty_pydantic_diff
|
|
21
31
|
|
|
22
32
|
|
|
23
33
|
if TYPE_CHECKING:
|
|
@@ -81,6 +91,17 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
81
91
|
" added.",
|
|
82
92
|
examples=["^(?!repomix)(.*)|^repomix.*pack_codebase.*$"],
|
|
83
93
|
)
|
|
94
|
+
include_default_tools: list[str] = Field(
|
|
95
|
+
default_factory=lambda: [tool.__name__ for tool in BUILT_IN_TOOLS],
|
|
96
|
+
description=(
|
|
97
|
+
"List of default tool class names to include. By default, the agent "
|
|
98
|
+
"includes 'FinishTool' and 'ThinkTool'. Set to an empty list to disable "
|
|
99
|
+
"all default tools, or provide a subset to include only specific ones. "
|
|
100
|
+
"Example: include_default_tools=['FinishTool'] to only include FinishTool, "
|
|
101
|
+
"or include_default_tools=[] to disable all default tools."
|
|
102
|
+
),
|
|
103
|
+
examples=[["FinishTool", "ThinkTool"], ["FinishTool"], []],
|
|
104
|
+
)
|
|
84
105
|
agent_context: AgentContext | None = Field(
|
|
85
106
|
default=None,
|
|
86
107
|
description="Optional AgentContext to initialize "
|
|
@@ -89,7 +110,7 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
89
110
|
{
|
|
90
111
|
"skills": [
|
|
91
112
|
{
|
|
92
|
-
"name": "
|
|
113
|
+
"name": "AGENTS.md",
|
|
93
114
|
"content": "When you see this message, you should reply like "
|
|
94
115
|
"you are a grumpy cat forced to use the internet.",
|
|
95
116
|
"type": "repo",
|
|
@@ -155,6 +176,7 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
155
176
|
|
|
156
177
|
# Runtime materialized tools; private and non-serializable
|
|
157
178
|
_tools: dict[str, ToolDefinition] = PrivateAttr(default_factory=dict)
|
|
179
|
+
_initialized: bool = PrivateAttr(default=False)
|
|
158
180
|
|
|
159
181
|
@property
|
|
160
182
|
def prompt_dir(self) -> str:
|
|
@@ -219,7 +241,7 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
219
241
|
def _initialize(self, state: "ConversationState"):
|
|
220
242
|
"""Create an AgentBase instance from an AgentSpec."""
|
|
221
243
|
|
|
222
|
-
if self.
|
|
244
|
+
if self._initialized:
|
|
223
245
|
logger.warning("Agent already initialized; skipping re-initialization.")
|
|
224
246
|
return
|
|
225
247
|
|
|
@@ -255,10 +277,17 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
255
277
|
f"{[tool.name for tool in tools]}",
|
|
256
278
|
)
|
|
257
279
|
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
for
|
|
261
|
-
|
|
280
|
+
# Include default tools from include_default_tools; not subject to regex
|
|
281
|
+
# filtering. Use explicit mapping to resolve tool class names.
|
|
282
|
+
for tool_name in self.include_default_tools:
|
|
283
|
+
tool_class = BUILT_IN_TOOL_CLASSES.get(tool_name)
|
|
284
|
+
if tool_class is None:
|
|
285
|
+
raise ValueError(
|
|
286
|
+
f"Unknown built-in tool class: '{tool_name}'. "
|
|
287
|
+
f"Expected one of: {list(BUILT_IN_TOOL_CLASSES.keys())}"
|
|
288
|
+
)
|
|
289
|
+
tool_instances = tool_class.create(state)
|
|
290
|
+
tools.extend(tool_instances)
|
|
262
291
|
|
|
263
292
|
# Check tool types
|
|
264
293
|
for tool in tools:
|
|
@@ -276,6 +305,7 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
276
305
|
|
|
277
306
|
# Store tools in a dict for easy access
|
|
278
307
|
self._tools = {tool.name: tool for tool in tools}
|
|
308
|
+
self._initialized = True
|
|
279
309
|
|
|
280
310
|
@abstractmethod
|
|
281
311
|
def step(
|
|
@@ -300,71 +330,85 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
300
330
|
NOTE: state will be mutated in-place.
|
|
301
331
|
"""
|
|
302
332
|
|
|
303
|
-
def
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
333
|
+
def verify(
|
|
334
|
+
self,
|
|
335
|
+
persisted: "AgentBase",
|
|
336
|
+
events: "Sequence[Any] | None" = None,
|
|
337
|
+
) -> "AgentBase":
|
|
338
|
+
"""Verify that we can resume this agent from persisted state.
|
|
339
|
+
|
|
340
|
+
This PR's goal is to *not* reconcile configuration between persisted and
|
|
341
|
+
runtime Agent instances. Instead, we verify compatibility requirements
|
|
342
|
+
and then continue with the runtime-provided Agent.
|
|
343
|
+
|
|
344
|
+
Compatibility requirements:
|
|
345
|
+
- Agent class/type must match.
|
|
346
|
+
- Tools:
|
|
347
|
+
- If events are provided, only tools that were actually used in history
|
|
348
|
+
must exist in runtime.
|
|
349
|
+
- If events are not provided, tool names must match exactly.
|
|
350
|
+
|
|
351
|
+
All other configuration (LLM, agent_context, condenser, system prompts,
|
|
352
|
+
etc.) can be freely changed between sessions.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
persisted: The agent loaded from persisted state.
|
|
356
|
+
events: Optional event sequence to scan for used tools if tool names
|
|
357
|
+
don't match.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
This runtime agent (self) if verification passes.
|
|
361
|
+
|
|
362
|
+
Raises:
|
|
363
|
+
ValueError: If agent class or tools don't match.
|
|
307
364
|
"""
|
|
308
365
|
if persisted.__class__ is not self.__class__:
|
|
309
366
|
raise ValueError(
|
|
310
|
-
|
|
367
|
+
"Cannot load from persisted: persisted agent is of type "
|
|
311
368
|
f"{persisted.__class__.__name__}, but self is of type "
|
|
312
369
|
f"{self.__class__.__name__}."
|
|
313
370
|
)
|
|
314
371
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
updates: dict[str, Any] = {"llm": new_llm}
|
|
372
|
+
runtime_names = {tool.name for tool in self.tools}
|
|
373
|
+
persisted_names = {tool.name for tool in persisted.tools}
|
|
318
374
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
# Check if both condensers are LLMSummarizingCondenser
|
|
322
|
-
# (which has an llm field)
|
|
375
|
+
if runtime_names == persisted_names:
|
|
376
|
+
return self
|
|
323
377
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
378
|
+
if events is not None:
|
|
379
|
+
from openhands.sdk.event import ActionEvent
|
|
380
|
+
|
|
381
|
+
used_tools = {
|
|
382
|
+
event.tool_name
|
|
383
|
+
for event in events
|
|
384
|
+
if isinstance(event, ActionEvent) and event.tool_name
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# Only require tools that were actually used in history.
|
|
388
|
+
missing_used_tools = used_tools - runtime_names
|
|
389
|
+
if missing_used_tools:
|
|
390
|
+
raise ValueError(
|
|
391
|
+
"Cannot resume conversation: tools that were used in history "
|
|
392
|
+
f"are missing from runtime: {sorted(missing_used_tools)}. "
|
|
393
|
+
f"Available tools: {sorted(runtime_names)}"
|
|
332
394
|
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
missing_in_runtime = persisted_names - runtime_names
|
|
351
|
-
missing_in_persisted = runtime_names - persisted_names
|
|
352
|
-
error_msg = "Tools don't match between runtime and persisted agents."
|
|
353
|
-
if missing_in_runtime:
|
|
354
|
-
error_msg += f" Missing in runtime: {missing_in_runtime}."
|
|
355
|
-
if missing_in_persisted:
|
|
356
|
-
error_msg += f" Missing in persisted: {missing_in_persisted}."
|
|
357
|
-
raise ValueError(error_msg)
|
|
358
|
-
|
|
359
|
-
reconciled = persisted.model_copy(update=updates)
|
|
360
|
-
if self.model_dump(exclude_none=True) != reconciled.model_dump(
|
|
361
|
-
exclude_none=True
|
|
362
|
-
):
|
|
363
|
-
raise ValueError(
|
|
364
|
-
"The Agent provided is different from the one in persisted state.\n"
|
|
365
|
-
f"Diff: {pretty_pydantic_diff(self, reconciled)}"
|
|
366
|
-
)
|
|
367
|
-
return reconciled
|
|
395
|
+
|
|
396
|
+
return self
|
|
397
|
+
|
|
398
|
+
# No events provided: strict tool name matching.
|
|
399
|
+
missing_in_runtime = persisted_names - runtime_names
|
|
400
|
+
missing_in_persisted = runtime_names - persisted_names
|
|
401
|
+
|
|
402
|
+
details: list[str] = []
|
|
403
|
+
if missing_in_runtime:
|
|
404
|
+
details.append(f"Missing in runtime: {sorted(missing_in_runtime)}")
|
|
405
|
+
if missing_in_persisted:
|
|
406
|
+
details.append(f"Missing in persisted: {sorted(missing_in_persisted)}")
|
|
407
|
+
|
|
408
|
+
suffix = f" ({'; '.join(details)})" if details else ""
|
|
409
|
+
raise ValueError(
|
|
410
|
+
"Tools don't match between runtime and persisted agents." + suffix
|
|
411
|
+
)
|
|
368
412
|
|
|
369
413
|
def model_dump_succint(self, **kwargs):
|
|
370
414
|
"""Like model_dump, but excludes None fields by default."""
|
|
@@ -452,6 +496,6 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
452
496
|
Raises:
|
|
453
497
|
RuntimeError: If the agent has not been initialized.
|
|
454
498
|
"""
|
|
455
|
-
if not self.
|
|
499
|
+
if not self._initialized:
|
|
456
500
|
raise RuntimeError("Agent not initialized; call initialize() before use")
|
|
457
501
|
return self._tools
|
|
@@ -6,7 +6,7 @@ You are OpenHands agent, a helpful AI assistant that can interact with a compute
|
|
|
6
6
|
</ROLE>
|
|
7
7
|
|
|
8
8
|
<MEMORY>
|
|
9
|
-
* Use
|
|
9
|
+
* Use `AGENTS.md` under the repository root as your persistent memory for repository-specific knowledge and context.
|
|
10
10
|
* Add important insights, patterns, and learnings to this file to improve future task performance.
|
|
11
11
|
* This repository skill is automatically loaded for every conversation and helps maintain context across sessions.
|
|
12
12
|
* For more information about skills, see: https://docs.openhands.dev/overview/skills
|
|
@@ -209,6 +209,9 @@ def make_llm_completion(
|
|
|
209
209
|
configured. This allows weaker models to omit risk field and bypass
|
|
210
210
|
validation requirements when analyzer is disabled. For detailed logic,
|
|
211
211
|
see `_extract_security_risk` method in agent.py.
|
|
212
|
+
|
|
213
|
+
Summary field is always added to tool schemas for transparency and
|
|
214
|
+
explainability of agent actions.
|
|
212
215
|
"""
|
|
213
216
|
if llm.uses_responses_api():
|
|
214
217
|
return llm.responses(
|
|
@@ -11,6 +11,7 @@ from openhands.sdk.context.skills import (
|
|
|
11
11
|
SkillKnowledge,
|
|
12
12
|
load_public_skills,
|
|
13
13
|
load_user_skills,
|
|
14
|
+
to_prompt,
|
|
14
15
|
)
|
|
15
16
|
from openhands.sdk.llm import Message, TextContent
|
|
16
17
|
from openhands.sdk.llm.utils.model_prompt_spec import get_model_prompt_spec
|
|
@@ -167,8 +168,32 @@ class AgentContext(BaseModel):
|
|
|
167
168
|
- Runtime information (e.g., available hosts, current date)
|
|
168
169
|
- Conversation instructions (e.g., user preferences, task details)
|
|
169
170
|
- Repository-specific instructions (collected from repo skills)
|
|
171
|
+
- Available skills list (for AgentSkills-format and triggered skills)
|
|
172
|
+
|
|
173
|
+
Skill categorization:
|
|
174
|
+
- AgentSkills-format (SKILL.md): Always in <available_skills> (progressive
|
|
175
|
+
disclosure). If has triggers, content is ALSO auto-injected on trigger
|
|
176
|
+
in user prompts.
|
|
177
|
+
- Legacy with trigger=None: Full content in <REPO_CONTEXT> (always active)
|
|
178
|
+
- Legacy with triggers: Listed in <available_skills>, injected on trigger
|
|
170
179
|
"""
|
|
171
|
-
|
|
180
|
+
# Categorize skills based on format and trigger:
|
|
181
|
+
# - AgentSkills-format: always in available_skills (progressive disclosure)
|
|
182
|
+
# - Legacy: trigger=None -> REPO_CONTEXT, else -> available_skills
|
|
183
|
+
repo_skills: list[Skill] = []
|
|
184
|
+
available_skills: list[Skill] = []
|
|
185
|
+
|
|
186
|
+
for s in self.skills:
|
|
187
|
+
if s.is_agentskills_format:
|
|
188
|
+
# AgentSkills: always list (triggers also auto-inject via
|
|
189
|
+
# get_user_message_suffix)
|
|
190
|
+
available_skills.append(s)
|
|
191
|
+
elif s.trigger is None:
|
|
192
|
+
# Legacy OpenHands: no trigger = full content in REPO_CONTEXT
|
|
193
|
+
repo_skills.append(s)
|
|
194
|
+
else:
|
|
195
|
+
# Legacy OpenHands: has trigger = list in available_skills
|
|
196
|
+
available_skills.append(s)
|
|
172
197
|
|
|
173
198
|
# Gate vendor-specific repo skills based on model family.
|
|
174
199
|
if llm_model or llm_model_canonical:
|
|
@@ -189,16 +214,32 @@ class AgentContext(BaseModel):
|
|
|
189
214
|
filtered.append(s)
|
|
190
215
|
repo_skills = filtered
|
|
191
216
|
|
|
192
|
-
logger.debug(f"
|
|
217
|
+
logger.debug(f"Loaded {len(repo_skills)} repository skills: {repo_skills}")
|
|
218
|
+
|
|
219
|
+
# Generate available skills prompt
|
|
220
|
+
available_skills_prompt = ""
|
|
221
|
+
if available_skills:
|
|
222
|
+
available_skills_prompt = to_prompt(available_skills)
|
|
223
|
+
logger.debug(
|
|
224
|
+
f"Generated available skills prompt for {len(available_skills)} skills"
|
|
225
|
+
)
|
|
226
|
+
|
|
193
227
|
# Build the workspace context information
|
|
194
228
|
secret_infos = self.get_secret_infos()
|
|
195
|
-
|
|
229
|
+
has_content = (
|
|
230
|
+
repo_skills
|
|
231
|
+
or self.system_message_suffix
|
|
232
|
+
or secret_infos
|
|
233
|
+
or available_skills_prompt
|
|
234
|
+
)
|
|
235
|
+
if has_content:
|
|
196
236
|
formatted_text = render_template(
|
|
197
237
|
prompt_dir=str(PROMPT_DIR),
|
|
198
238
|
template_name="system_message_suffix.j2",
|
|
199
239
|
repo_skills=repo_skills,
|
|
200
240
|
system_message_suffix=self.system_message_suffix or "",
|
|
201
241
|
secret_infos=secret_infos,
|
|
242
|
+
available_skills_prompt=available_skills_prompt,
|
|
202
243
|
).strip()
|
|
203
244
|
return formatted_text
|
|
204
245
|
elif self.system_message_suffix and self.system_message_suffix.strip():
|
|
@@ -245,6 +286,7 @@ class AgentContext(BaseModel):
|
|
|
245
286
|
name=skill.name,
|
|
246
287
|
trigger=trigger,
|
|
247
288
|
content=skill.content,
|
|
289
|
+
location=skill.source,
|
|
248
290
|
)
|
|
249
291
|
)
|
|
250
292
|
if recalled_knowledge:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from openhands.sdk.context.condenser.base import (
|
|
2
2
|
CondenserBase,
|
|
3
|
+
NoCondensationAvailableException,
|
|
3
4
|
RollingCondenser,
|
|
4
5
|
)
|
|
5
6
|
from openhands.sdk.context.condenser.llm_summarizing_condenser import (
|
|
@@ -15,4 +16,5 @@ __all__ = [
|
|
|
15
16
|
"NoOpCondenser",
|
|
16
17
|
"PipelineCondenser",
|
|
17
18
|
"LLMSummarizingCondenser",
|
|
19
|
+
"NoCondensationAvailableException",
|
|
18
20
|
]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import Enum
|
|
2
3
|
from logging import getLogger
|
|
3
4
|
|
|
4
5
|
from openhands.sdk.context.view import View
|
|
@@ -66,6 +67,29 @@ class PipelinableCondenserBase(CondenserBase):
|
|
|
66
67
|
condenser should not nest another pipeline condenser)"""
|
|
67
68
|
|
|
68
69
|
|
|
70
|
+
class NoCondensationAvailableException(Exception):
|
|
71
|
+
"""Raised when a condenser is asked to provide a condensation but none is available.
|
|
72
|
+
|
|
73
|
+
This can happen if the condenser's `should_condense` method returns True, but due to
|
|
74
|
+
API constraints no condensation can be generated.
|
|
75
|
+
|
|
76
|
+
When this exception is raised from a rolling condenser's `get_condensation` method,
|
|
77
|
+
the agent will fall back to using the uncondensed view for the next agent step.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CondensationRequirement(Enum):
|
|
82
|
+
"""The type of condensation required by a rolling condenser."""
|
|
83
|
+
|
|
84
|
+
HARD = "hard"
|
|
85
|
+
"""Indicates that a condensation is required right now, and the agent cannot proceed
|
|
86
|
+
without it.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
SOFT = "soft"
|
|
90
|
+
"""Indicates that a condensation is desired but not strictly required."""
|
|
91
|
+
|
|
92
|
+
|
|
69
93
|
class RollingCondenser(PipelinableCondenserBase, ABC):
|
|
70
94
|
"""Base class for a specialized condenser strategy that applies condensation to a
|
|
71
95
|
rolling history.
|
|
@@ -73,15 +97,27 @@ class RollingCondenser(PipelinableCondenserBase, ABC):
|
|
|
73
97
|
The rolling history is generated by `View.from_events`, which analyzes all events in
|
|
74
98
|
the history and produces a `View` object representing what will be sent to the LLM.
|
|
75
99
|
|
|
76
|
-
If `
|
|
77
|
-
`Condensation` object from the `View` object. This will be added to the
|
|
78
|
-
history which should -- when given to `get_view` -- produce the condensed
|
|
79
|
-
be passed to the LLM.
|
|
100
|
+
If `condensation_requirement` says so, the condenser is then responsible for
|
|
101
|
+
generating a `Condensation` object from the `View` object. This will be added to the
|
|
102
|
+
event history which should -- when given to `get_view` -- produce the condensed
|
|
103
|
+
`View` to be passed to the LLM.
|
|
80
104
|
"""
|
|
81
105
|
|
|
82
106
|
@abstractmethod
|
|
83
|
-
def
|
|
84
|
-
|
|
107
|
+
def condensation_requirement(
|
|
108
|
+
self, view: View, agent_llm: LLM | None = None
|
|
109
|
+
) -> CondensationRequirement | None:
|
|
110
|
+
"""Determine how a view should be condensed.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
view: The current view of the conversation history.
|
|
114
|
+
agent_llm: LLM instance used by the agent. Condensers use this for token
|
|
115
|
+
counting purposes. Defaults to None.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
CondensationRequirement | None: The type of condensation required, or None
|
|
119
|
+
if no condensation is needed.
|
|
120
|
+
"""
|
|
85
121
|
|
|
86
122
|
@abstractmethod
|
|
87
123
|
def get_condensation(
|
|
@@ -92,8 +128,23 @@ class RollingCondenser(PipelinableCondenserBase, ABC):
|
|
|
92
128
|
def condense(self, view: View, agent_llm: LLM | None = None) -> View | Condensation:
|
|
93
129
|
# If we trigger the condenser-specific condensation threshold, compute and
|
|
94
130
|
# return the condensation.
|
|
95
|
-
|
|
96
|
-
|
|
131
|
+
request = self.condensation_requirement(view, agent_llm=agent_llm)
|
|
132
|
+
if request is not None:
|
|
133
|
+
try:
|
|
134
|
+
return self.get_condensation(view, agent_llm=agent_llm)
|
|
135
|
+
|
|
136
|
+
except NoCondensationAvailableException as e:
|
|
137
|
+
logger.debug(f"No condensation available: {e}")
|
|
138
|
+
|
|
139
|
+
if request == CondensationRequirement.SOFT:
|
|
140
|
+
# For soft requests, we can just return the uncondensed view. This
|
|
141
|
+
# request will _eventually_ be handled, but it's not critical that
|
|
142
|
+
# we do so immediately.
|
|
143
|
+
return view
|
|
144
|
+
|
|
145
|
+
# Otherwise re-raise the exception.
|
|
146
|
+
else:
|
|
147
|
+
raise e
|
|
97
148
|
|
|
98
149
|
# Otherwise we're safe to just return the view.
|
|
99
150
|
else:
|
|
@@ -4,7 +4,11 @@ from enum import Enum
|
|
|
4
4
|
|
|
5
5
|
from pydantic import Field, model_validator
|
|
6
6
|
|
|
7
|
-
from openhands.sdk.context.condenser.base import
|
|
7
|
+
from openhands.sdk.context.condenser.base import (
|
|
8
|
+
CondensationRequirement,
|
|
9
|
+
NoCondensationAvailableException,
|
|
10
|
+
RollingCondenser,
|
|
11
|
+
)
|
|
8
12
|
from openhands.sdk.context.condenser.utils import (
|
|
9
13
|
get_suffix_length_for_token_reduction,
|
|
10
14
|
get_total_token_count,
|
|
@@ -84,9 +88,30 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
84
88
|
|
|
85
89
|
return reasons
|
|
86
90
|
|
|
87
|
-
def
|
|
91
|
+
def condensation_requirement(
|
|
92
|
+
self, view: View, agent_llm: LLM | None = None
|
|
93
|
+
) -> CondensationRequirement | None:
|
|
88
94
|
reasons = self.get_condensation_reasons(view, agent_llm)
|
|
89
|
-
|
|
95
|
+
|
|
96
|
+
# No reasons => no condensation needed.
|
|
97
|
+
if reasons == set():
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
# If the reasons are for resource constraints, we can treat it as a soft
|
|
101
|
+
# requirement. We want to condense when we can, but there's still space in the
|
|
102
|
+
# context window or we'd also see Reason.REQUEST. That means we can delay the
|
|
103
|
+
# condensation if there isn't one available (based on the view's manipulation
|
|
104
|
+
# indices).
|
|
105
|
+
resource_reasons = {Reason.TOKENS, Reason.EVENTS}
|
|
106
|
+
if reasons.issubset(resource_reasons):
|
|
107
|
+
return CondensationRequirement.SOFT
|
|
108
|
+
|
|
109
|
+
# Requests -- whether they come from the user or the agent -- are always hard
|
|
110
|
+
# requirements. We need to condense now because:
|
|
111
|
+
# 1. the user expects it
|
|
112
|
+
# 2. the agent has no more room in the context window and can't continue
|
|
113
|
+
if Reason.REQUEST in reasons:
|
|
114
|
+
return CondensationRequirement.HARD
|
|
90
115
|
|
|
91
116
|
def _get_summary_event_content(self, view: View) -> str:
|
|
92
117
|
"""Extract the text content from the summary event in the view, if any.
|
|
@@ -124,12 +149,7 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
124
149
|
Raises:
|
|
125
150
|
ValueError: If forgotten_events is empty (0 events to condense).
|
|
126
151
|
"""
|
|
127
|
-
|
|
128
|
-
raise ValueError(
|
|
129
|
-
"Cannot condense 0 events. This typically occurs when a tool loop "
|
|
130
|
-
"spans almost the entire view, leaving no valid range for forgetting "
|
|
131
|
-
"events. Consider adjusting keep_first or max_size parameters."
|
|
132
|
-
)
|
|
152
|
+
assert len(forgotten_events) > 0, "No events to condense."
|
|
133
153
|
|
|
134
154
|
# Convert events to strings for the template
|
|
135
155
|
event_strings = [str(forgotten_event) for forgotten_event in forgotten_events]
|
|
@@ -236,11 +256,19 @@ class LLMSummarizingCondenser(RollingCondenser):
|
|
|
236
256
|
) -> Condensation:
|
|
237
257
|
# The condensation is dependent on the events we want to drop and the previous
|
|
238
258
|
# summary.
|
|
239
|
-
summary_event_content = self._get_summary_event_content(view)
|
|
240
259
|
forgotten_events, summary_offset = self._get_forgotten_events(
|
|
241
260
|
view, agent_llm=agent_llm
|
|
242
261
|
)
|
|
243
262
|
|
|
263
|
+
if not forgotten_events:
|
|
264
|
+
raise NoCondensationAvailableException(
|
|
265
|
+
"Cannot condense 0 events. This typically occurs when a tool loop "
|
|
266
|
+
"spans almost the entire view, leaving no valid range for forgetting "
|
|
267
|
+
"events. Consider adjusting keep_first or max_size parameters."
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
summary_event_content = self._get_summary_event_content(view)
|
|
271
|
+
|
|
244
272
|
return self._generate_condensation(
|
|
245
273
|
summary_event_content=summary_event_content,
|
|
246
274
|
forgotten_events=forgotten_events,
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<EXTRA_INFO>
|
|
3
3
|
The following information has been included based on a keyword match for "{{ agent_info.trigger }}".
|
|
4
4
|
It may or may not be relevant to the user's request.
|
|
5
|
+
{% if agent_info.location %}
|
|
6
|
+
Skill location: {{ agent_info.location }}
|
|
7
|
+
(Use this path to resolve relative file references in the skill content below)
|
|
8
|
+
{% endif %}
|
|
5
9
|
|
|
6
10
|
{{ agent_info.content }}
|
|
7
11
|
</EXTRA_INFO>
|