openhands-sdk 1.58.2__tar.gz → 1.60.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.58.2 → openhands_sdk-1.60.0}/PKG-INFO +4 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/__init__.py +4 -14
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_agent.py +152 -16
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_file_credentials.py +57 -3
- openhands_sdk-1.60.0/openhands/sdk/agent/acp_skills.py +67 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/agent.py +53 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/base.py +11 -9
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/stream_context.py +35 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/utils.py +36 -19
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/static.py +4 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/base.py +11 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/conversation.py +5 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/event_store.py +132 -57
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/exceptions.py +25 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/impl/local_conversation.py +179 -56
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/impl/remote_conversation.py +64 -24
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/request.py +18 -6
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/response_utils.py +11 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/secret_registry.py +13 -11
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/state.py +36 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/stuck_detector.py +163 -18
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/title_utils.py +5 -11
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/taxonomy.py +4 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/__init__.py +2 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/acp_tool_call.py +2 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/base.py +3 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/hook_execution.py +3 -3
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_completion_log.py +2 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/observation.py +14 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/types.py +13 -3
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/fetch.py +21 -12
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/manager.py +17 -7
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/git/git_changes.py +41 -92
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/git/git_commits.py +3 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/git/utils.py +11 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/io/memory.py +11 -3
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/launch/finalize.py +4 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/launch/preview.py +1 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/launch/resolve.py +1 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/__init__.py +29 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/auth/openai.py +4 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/call_context.py +8 -0
- openhands_sdk-1.60.0/openhands/sdk/llm/decision.py +580 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/classifier.py +31 -5
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/mapping.py +7 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm.py +60 -11
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm_profile_store.py +1 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/message.py +11 -12
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/mixins/fn_call_converter.py +20 -5
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/responses_options.py +1 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/model_features.py +79 -6
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/model_info.py +32 -10
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/openhands_provider.py +16 -0
- openhands_sdk-1.60.0/openhands/sdk/llm/utils/retry_after.py +67 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/retry_mixin.py +38 -6
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/verified_models.py +16 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/tool.py +27 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/profiles/__init__.py +0 -6
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/profiles/agent_profile.py +24 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/profiles/resolver.py +7 -215
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/profiles/seed.py +1 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/grayswan/analyzer.py +7 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/settings/__init__.py +0 -6
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/settings/acp_install_catalog.py +1 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/settings/acp_providers.py +0 -209
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/settings/model.py +47 -7
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/skill.py +4 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/utils.py +14 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/subagent/registry.py +24 -5
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/subagent/schema.py +4 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/__init__.py +9 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/schema.py +8 -1
- openhands_sdk-1.60.0/openhands/sdk/utils/masking.py +70 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/redact.py +3 -1
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/base.py +2 -2
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/repo.py +99 -10
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/PKG-INFO +4 -4
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/SOURCES.txt +6 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/requires.txt +3 -3
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/pyproject.toml +4 -4
- openhands_sdk-1.58.2/openhands/sdk/utils/masking.py +0 -13
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_contracts.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_model_discovery.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/automation/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/message_request.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/security.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/launch/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/launch/errors.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/_message_normalization.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/_response_stream.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/_tokenizer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/meta_profile_store.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/logger/_rich_handler.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/oauth.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/paths.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/strict_fetch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/subagent/scope.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.58.2 → openhands_sdk-1.60.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.60.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
|
|
@@ -14,9 +14,9 @@ Requires-Dist: fakeredis[lua]>=2.32.1
|
|
|
14
14
|
Requires-Dist: fastmcp<4,>=3.2.0
|
|
15
15
|
Requires-Dist: filelock>=3.20.1
|
|
16
16
|
Requires-Dist: httpx[socks]>=0.28.1
|
|
17
|
-
Requires-Dist: jsonschema>=4.
|
|
17
|
+
Requires-Dist: jsonschema>=4.26.0
|
|
18
18
|
Requires-Dist: joserfc>=1.7.5
|
|
19
|
-
Requires-Dist: litellm<1.106,>=1.
|
|
19
|
+
Requires-Dist: litellm<1.106,>=1.104.3
|
|
20
20
|
Requires-Dist: pillow>=12.3.0
|
|
21
21
|
Requires-Dist: pydantic>=2.13.5
|
|
22
22
|
Requires-Dist: python-frontmatter>=1.3.0
|
|
@@ -31,4 +31,4 @@ Requires-Dist: boto3>=1.35.0; extra == "boto3"
|
|
|
31
31
|
Provides-Extra: toolshield
|
|
32
32
|
Requires-Dist: toolshield<0.2,>=0.1.3; extra == "toolshield"
|
|
33
33
|
Provides-Extra: vertex
|
|
34
|
-
Requires-Dist: google-cloud-aiplatform>=2.
|
|
34
|
+
Requires-Dist: google-cloud-aiplatform>=2.5.0; extra == "vertex"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
5
4
|
|
|
6
5
|
from openhands.sdk.agent import (
|
|
7
6
|
Agent,
|
|
@@ -27,6 +26,8 @@ from openhands.sdk.io import FileStore, LocalFileStore
|
|
|
27
26
|
from openhands.sdk.llm import (
|
|
28
27
|
LLM,
|
|
29
28
|
LLM_PROFILE_SCHEMA_VERSION,
|
|
29
|
+
DecisionModel,
|
|
30
|
+
DecisionResult,
|
|
30
31
|
FallbackStrategy,
|
|
31
32
|
ImageContent,
|
|
32
33
|
LLMProfileStore,
|
|
@@ -109,10 +110,6 @@ from openhands.sdk.workspace import (
|
|
|
109
110
|
)
|
|
110
111
|
|
|
111
112
|
|
|
112
|
-
if TYPE_CHECKING:
|
|
113
|
-
from openhands.sdk.settings.acp_providers import ACPModelOption
|
|
114
|
-
|
|
115
|
-
|
|
116
113
|
try:
|
|
117
114
|
__version__ = version("openhands-sdk")
|
|
118
115
|
except PackageNotFoundError:
|
|
@@ -124,6 +121,8 @@ _print_banner(__version__)
|
|
|
124
121
|
|
|
125
122
|
__all__ = [
|
|
126
123
|
"LLM",
|
|
124
|
+
"DecisionModel",
|
|
125
|
+
"DecisionResult",
|
|
127
126
|
"LLM_PROFILE_SCHEMA_VERSION",
|
|
128
127
|
"LLMRegistry",
|
|
129
128
|
"LLMProfileStore",
|
|
@@ -167,7 +166,6 @@ __all__ = [
|
|
|
167
166
|
"ACP_PROVIDERS",
|
|
168
167
|
"ACPAgentSettings",
|
|
169
168
|
"ACPFileSecretSpec",
|
|
170
|
-
"ACPModelOption",
|
|
171
169
|
"ACPProviderInfo",
|
|
172
170
|
"AgentSettingsBase",
|
|
173
171
|
"AgentSettingsConfig",
|
|
@@ -211,11 +209,3 @@ __all__ = [
|
|
|
211
209
|
"page_iterator",
|
|
212
210
|
"__version__",
|
|
213
211
|
]
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def __getattr__(name: str) -> Any:
|
|
217
|
-
if name == "ACPModelOption":
|
|
218
|
-
from openhands.sdk.settings.acp_providers import _deprecated_model_option
|
|
219
|
-
|
|
220
|
-
return _deprecated_model_option()
|
|
221
|
-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -19,10 +19,12 @@ from __future__ import annotations
|
|
|
19
19
|
import asyncio
|
|
20
20
|
import atexit
|
|
21
21
|
import contextlib
|
|
22
|
+
import functools
|
|
22
23
|
import inspect
|
|
23
24
|
import json
|
|
24
25
|
import os
|
|
25
26
|
import re
|
|
27
|
+
import shutil
|
|
26
28
|
import threading
|
|
27
29
|
import time
|
|
28
30
|
import uuid
|
|
@@ -106,6 +108,7 @@ from openhands.sdk.agent.acp_file_credentials import (
|
|
|
106
108
|
write_secret_file,
|
|
107
109
|
)
|
|
108
110
|
from openhands.sdk.agent.acp_models import ACPModelInfo
|
|
111
|
+
from openhands.sdk.agent.acp_skills import render_skill_catalog, write_skill_files
|
|
109
112
|
from openhands.sdk.agent.acp_tracing import ACPTurnTrace
|
|
110
113
|
from openhands.sdk.agent.base import AgentBase
|
|
111
114
|
from openhands.sdk.agent.stream_context import StreamContext
|
|
@@ -1340,6 +1343,14 @@ class _OpenHandsACPBridge:
|
|
|
1340
1343
|
self.accumulated_text: list[str] = []
|
|
1341
1344
|
self.accumulated_thoughts: list[str] = []
|
|
1342
1345
|
self.accumulated_tool_calls: list[dict[str, Any]] = []
|
|
1346
|
+
# Text and thoughts said before a tool call are emitted as their own
|
|
1347
|
+
# message ahead of that call (``on_text_segment``), so the transcript
|
|
1348
|
+
# keeps the agent's narration where it was said. These mark where the
|
|
1349
|
+
# segment not yet emitted starts; the rest of the turn ends it.
|
|
1350
|
+
self._segment_text_start = 0
|
|
1351
|
+
self._segment_thought_start = 0
|
|
1352
|
+
self.text_segments_emitted = 0
|
|
1353
|
+
self.on_text_segment: Callable[[str, str], None] | None = None
|
|
1343
1354
|
# Emits the LLM/TOOL spans an ACP turn would otherwise never produce.
|
|
1344
1355
|
self.trace = ACPTurnTrace(acp_server=None, model_id=None)
|
|
1345
1356
|
self.on_token: Any = None # ConversationTokenCallbackType | None
|
|
@@ -1385,6 +1396,10 @@ class _OpenHandsACPBridge:
|
|
|
1385
1396
|
self.accumulated_text.clear()
|
|
1386
1397
|
self.accumulated_thoughts.clear()
|
|
1387
1398
|
self.accumulated_tool_calls.clear()
|
|
1399
|
+
self._segment_text_start = 0
|
|
1400
|
+
self._segment_thought_start = 0
|
|
1401
|
+
self.text_segments_emitted = 0
|
|
1402
|
+
self.on_text_segment = None
|
|
1388
1403
|
self.on_token = None
|
|
1389
1404
|
self.on_event = None
|
|
1390
1405
|
self.on_activity = None
|
|
@@ -1527,6 +1542,7 @@ class _OpenHandsACPBridge:
|
|
|
1527
1542
|
if event is not None:
|
|
1528
1543
|
event.set()
|
|
1529
1544
|
elif isinstance(update, ToolCallStart):
|
|
1545
|
+
self._emit_text_segment()
|
|
1530
1546
|
entry = {
|
|
1531
1547
|
"tool_call_id": update.tool_call_id,
|
|
1532
1548
|
"title": update.title,
|
|
@@ -1597,6 +1613,41 @@ class _OpenHandsACPBridge:
|
|
|
1597
1613
|
else:
|
|
1598
1614
|
logger.debug("ACP session update: %s", type(update).__name__)
|
|
1599
1615
|
|
|
1616
|
+
def segment_text(self) -> str:
|
|
1617
|
+
"""Text said since the last emitted segment (the whole turn if none)."""
|
|
1618
|
+
return "".join(self.accumulated_text[self._segment_text_start :])
|
|
1619
|
+
|
|
1620
|
+
def segment_thoughts(self) -> str:
|
|
1621
|
+
"""Thoughts since the last emitted segment (the whole turn if none)."""
|
|
1622
|
+
return "".join(self.accumulated_thoughts[self._segment_thought_start :])
|
|
1623
|
+
|
|
1624
|
+
def _emit_text_segment(self) -> None:
|
|
1625
|
+
"""Hand the text and thoughts said so far to ``on_text_segment``.
|
|
1626
|
+
|
|
1627
|
+
Called when a tool call starts, so that narration such as "let me run
|
|
1628
|
+
the tests" lands before the tool call it announces instead of joining
|
|
1629
|
+
the turn's final message. The joined segment is masked again because a
|
|
1630
|
+
secret split across chunks only reassembles here.
|
|
1631
|
+
"""
|
|
1632
|
+
if self.on_text_segment is None:
|
|
1633
|
+
return
|
|
1634
|
+
text = self.segment_text()
|
|
1635
|
+
thoughts = self.segment_thoughts()
|
|
1636
|
+
if not text.strip() and not thoughts.strip():
|
|
1637
|
+
return
|
|
1638
|
+
try:
|
|
1639
|
+
self.on_text_segment(self._mask_value(text), self._mask_value(thoughts))
|
|
1640
|
+
except CredentialBindingError:
|
|
1641
|
+
# Keep the segment: the turn's final text carries it, and the
|
|
1642
|
+
# recorded masking error fails the turn before anything persists.
|
|
1643
|
+
return
|
|
1644
|
+
except Exception:
|
|
1645
|
+
logger.debug("on_text_segment callback failed", exc_info=True)
|
|
1646
|
+
return
|
|
1647
|
+
self._segment_text_start = len(self.accumulated_text)
|
|
1648
|
+
self._segment_thought_start = len(self.accumulated_thoughts)
|
|
1649
|
+
self.text_segments_emitted += 1
|
|
1650
|
+
|
|
1600
1651
|
def _emit_tool_call_event(self, tc: dict[str, Any]) -> None:
|
|
1601
1652
|
"""Emit an ACPToolCallEvent reflecting the current state of ``tc``.
|
|
1602
1653
|
|
|
@@ -2020,6 +2071,10 @@ class ACPAgent(AgentBase):
|
|
|
2020
2071
|
# "installed" — already in subprocess history; skip further injection
|
|
2021
2072
|
_suffix_install_state: str = PrivateAttr(default="unused")
|
|
2022
2073
|
_installed_suffix: str | None = PrivateAttr(default=None)
|
|
2074
|
+
# Skills written to disk and listed by path in the prompt, not inlined.
|
|
2075
|
+
_skill_locations: dict[str, Path] = PrivateAttr(default_factory=dict)
|
|
2076
|
+
_skills_root: Path | None = PrivateAttr(default=None)
|
|
2077
|
+
_additional_directories_supported: bool = PrivateAttr(default=False)
|
|
2023
2078
|
_restart_session_on_next_turn: bool = PrivateAttr(default=False)
|
|
2024
2079
|
# Stream identity for the turn in flight; see stream_context.py. Held on
|
|
2025
2080
|
# the agent rather than threaded through the finalizers because the ACP
|
|
@@ -2331,6 +2386,7 @@ class ACPAgent(AgentBase):
|
|
|
2331
2386
|
|
|
2332
2387
|
# Render the suffix once, pulling secrets from the conversation's
|
|
2333
2388
|
# secret_registry to match the regular Agent's get_dynamic_context().
|
|
2389
|
+
self._materialize_skills(state)
|
|
2334
2390
|
self._installed_suffix = self._render_suffix(state)
|
|
2335
2391
|
# A prior session id means we may be resuming; used by ``truly_resumed``
|
|
2336
2392
|
# below to decide whether the model state reported for this launch
|
|
@@ -2493,6 +2549,25 @@ class ACPAgent(AgentBase):
|
|
|
2493
2549
|
)
|
|
2494
2550
|
)
|
|
2495
2551
|
|
|
2552
|
+
def _skill_directories(self) -> list[str] | None:
|
|
2553
|
+
if self._skills_root and self._additional_directories_supported:
|
|
2554
|
+
return [str(self._skills_root)]
|
|
2555
|
+
return None
|
|
2556
|
+
|
|
2557
|
+
def _materialize_skills(self, state: ConversationState) -> None:
|
|
2558
|
+
self._skill_locations = {}
|
|
2559
|
+
self._skills_root = None
|
|
2560
|
+
context = self.agent_context
|
|
2561
|
+
if context is None or not context.skills:
|
|
2562
|
+
return
|
|
2563
|
+
_, available = context._partition_skills()
|
|
2564
|
+
if not available:
|
|
2565
|
+
return
|
|
2566
|
+
root = self._acp_file_secret_dir(state, "skills")
|
|
2567
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
2568
|
+
self._skill_locations = write_skill_files(root, available)
|
|
2569
|
+
self._skills_root = root
|
|
2570
|
+
|
|
2496
2571
|
def _render_suffix(self, state: ConversationState) -> str | None:
|
|
2497
2572
|
"""Render the system suffix once, including secrets from the registry.
|
|
2498
2573
|
|
|
@@ -2533,12 +2608,23 @@ class ACPAgent(AgentBase):
|
|
|
2533
2608
|
if not secret_infos:
|
|
2534
2609
|
return None
|
|
2535
2610
|
agent_context = AgentContext(current_datetime=None)
|
|
2536
|
-
|
|
2611
|
+
listed = [s for s in agent_context.skills if s.name in self._skill_locations]
|
|
2612
|
+
if listed:
|
|
2613
|
+
agent_context = agent_context.model_copy(
|
|
2614
|
+
update={"skills": [s for s in agent_context.skills if s not in listed]}
|
|
2615
|
+
)
|
|
2616
|
+
if agent_context.secrets:
|
|
2537
2617
|
# The registry already carries these (and their descriptions), so
|
|
2538
2618
|
# clear the agent_context copy to advertise from the registry alone
|
|
2539
2619
|
# rather than re-merging a redundant second source.
|
|
2540
2620
|
agent_context = agent_context.model_copy(update={"secrets": {}})
|
|
2541
|
-
|
|
2621
|
+
rendered = agent_context.to_acp_prompt_context(
|
|
2622
|
+
additional_secret_infos=secret_infos
|
|
2623
|
+
)
|
|
2624
|
+
if not listed:
|
|
2625
|
+
return rendered
|
|
2626
|
+
catalog = render_skill_catalog(listed, self._skill_locations)
|
|
2627
|
+
return f"{rendered}\n\n{catalog}" if rendered else catalog
|
|
2542
2628
|
|
|
2543
2629
|
def _present_file_secret_names(self, state: ConversationState) -> set[str]:
|
|
2544
2630
|
"""Reserved file-content secret names supplied for this conversation.
|
|
@@ -2768,16 +2854,19 @@ class ACPAgent(AgentBase):
|
|
|
2768
2854
|
if self._closed:
|
|
2769
2855
|
raise CredentialSyncError("Credential binding is closed.")
|
|
2770
2856
|
try:
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
)
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2857
|
+
# The CLI keeps its state in the conversation's durable
|
|
2858
|
+
# directory; the lifecycle links the credential in from a
|
|
2859
|
+
# temporary file, so it never persists there.
|
|
2860
|
+
home = self._acp_file_secret_dir(state, spec.subdir)
|
|
2861
|
+
lifecycle.materialize(state.secret_registry, env, home=home)
|
|
2862
|
+
durable_path = home / spec.filename
|
|
2863
|
+
if lifecycle.path != durable_path:
|
|
2864
|
+
try:
|
|
2865
|
+
durable_path.unlink(missing_ok=True)
|
|
2866
|
+
except OSError as exc:
|
|
2867
|
+
raise CredentialSyncError(
|
|
2868
|
+
"Durable credential copy could not be removed."
|
|
2869
|
+
) from exc
|
|
2781
2870
|
except BaseException:
|
|
2782
2871
|
env.pop("CODEX_HOME", None)
|
|
2783
2872
|
lifecycle.discard()
|
|
@@ -3161,6 +3250,15 @@ class ACPAgent(AgentBase):
|
|
|
3161
3250
|
if init_response.agent_capabilities is not None
|
|
3162
3251
|
else None
|
|
3163
3252
|
)
|
|
3253
|
+
session_caps = (
|
|
3254
|
+
init_response.agent_capabilities.session_capabilities
|
|
3255
|
+
if init_response.agent_capabilities is not None
|
|
3256
|
+
else None
|
|
3257
|
+
)
|
|
3258
|
+
self._additional_directories_supported = (
|
|
3259
|
+
session_caps is not None
|
|
3260
|
+
and session_caps.additional_directories is not None
|
|
3261
|
+
)
|
|
3164
3262
|
acp_mcp_servers = _mcp_config_to_acp_servers(self.mcp_config, mcp_caps)
|
|
3165
3263
|
if acp_mcp_servers:
|
|
3166
3264
|
logger.info(
|
|
@@ -3256,6 +3354,7 @@ class ACPAgent(AgentBase):
|
|
|
3256
3354
|
load_response = await conn.load_session(
|
|
3257
3355
|
cwd=working_dir,
|
|
3258
3356
|
session_id=prior_session_id,
|
|
3357
|
+
additional_directories=self._skill_directories(),
|
|
3259
3358
|
mcp_servers=acp_mcp_servers,
|
|
3260
3359
|
)
|
|
3261
3360
|
session_id = prior_session_id
|
|
@@ -3301,6 +3400,7 @@ class ACPAgent(AgentBase):
|
|
|
3301
3400
|
session_meta = build_session_model_meta(agent_name, self.acp_model)
|
|
3302
3401
|
response = await conn.new_session(
|
|
3303
3402
|
cwd=working_dir,
|
|
3403
|
+
additional_directories=self._skill_directories(),
|
|
3304
3404
|
mcp_servers=acp_mcp_servers,
|
|
3305
3405
|
**session_meta,
|
|
3306
3406
|
)
|
|
@@ -3421,11 +3521,41 @@ class ACPAgent(AgentBase):
|
|
|
3421
3521
|
self._client.trace.start_turn(prompt)
|
|
3422
3522
|
self._client.on_token = on_token
|
|
3423
3523
|
self._client.on_event = on_event
|
|
3524
|
+
self._client.on_text_segment = functools.partial(
|
|
3525
|
+
self._emit_text_segment, on_event
|
|
3526
|
+
)
|
|
3424
3527
|
self._client.on_activity = self._on_activity
|
|
3425
3528
|
# Start the idle-timeout clock fresh for this attempt so the deadline
|
|
3426
3529
|
# is measured from the send (or retry), not from a stale value.
|
|
3427
3530
|
self._client.arm_activity_clock()
|
|
3428
3531
|
|
|
3532
|
+
def _emit_text_segment(
|
|
3533
|
+
self, on_event: ConversationCallbackType, text: str, thoughts: str
|
|
3534
|
+
) -> None:
|
|
3535
|
+
"""Emit text the agent said before a tool call as its own message.
|
|
3536
|
+
|
|
3537
|
+
It retires the stream slot that carried this text and opens a new one,
|
|
3538
|
+
so the rest of the turn streams below the tool call.
|
|
3539
|
+
"""
|
|
3540
|
+
minted: dict[str, Any] = {}
|
|
3541
|
+
if self._stream is not None and (item_id := self._stream.claim()):
|
|
3542
|
+
minted["id"] = item_id
|
|
3543
|
+
on_event(
|
|
3544
|
+
MessageEvent(
|
|
3545
|
+
**minted,
|
|
3546
|
+
source="agent",
|
|
3547
|
+
llm_message=Message(
|
|
3548
|
+
role="assistant",
|
|
3549
|
+
content=[TextContent(text=text)],
|
|
3550
|
+
reasoning_content=thoughts or None,
|
|
3551
|
+
),
|
|
3552
|
+
)
|
|
3553
|
+
)
|
|
3554
|
+
if self._stream is not None:
|
|
3555
|
+
if minted:
|
|
3556
|
+
self._stream.commit()
|
|
3557
|
+
self._stream.next_item()
|
|
3558
|
+
|
|
3429
3559
|
def _cancel_inflight_tool_calls(self) -> None:
|
|
3430
3560
|
"""Emit a terminal ``failed`` ACPToolCallEvent for every tool call
|
|
3431
3561
|
in the accumulator that has not reached a terminal status yet.
|
|
@@ -3809,13 +3939,18 @@ class ACPAgent(AgentBase):
|
|
|
3809
3939
|
# gets caught before landing in the persisted event stream.
|
|
3810
3940
|
self._track_file_credentials_for_masking()
|
|
3811
3941
|
mask = state.secret_registry.mask_secrets_in_output
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3942
|
+
# Text said before a tool call was already emitted as its own message
|
|
3943
|
+
# (``_emit_text_segment``); the finish carries what the agent said
|
|
3944
|
+
# after its last tool call.
|
|
3945
|
+
response_text = mask(self._client.segment_text())
|
|
3946
|
+
thought_text = mask(self._client.segment_thoughts())
|
|
3947
|
+
if not response_text and not self._client.text_segments_emitted:
|
|
3815
3948
|
response_text = "(No response from ACP server)"
|
|
3816
3949
|
|
|
3817
3950
|
self._client.trace.finish_turn(
|
|
3818
|
-
|
|
3951
|
+
mask("".join(self._client.accumulated_text)) or response_text,
|
|
3952
|
+
mask("".join(self._client.accumulated_thoughts)),
|
|
3953
|
+
self._client.accumulated_tool_calls,
|
|
3819
3954
|
)
|
|
3820
3955
|
|
|
3821
3956
|
# ACP step() boundaries are full remote assistant turns, not
|
|
@@ -3991,6 +4126,7 @@ class ACPAgent(AgentBase):
|
|
|
3991
4126
|
return
|
|
3992
4127
|
self._client.trace.abandon()
|
|
3993
4128
|
self._client.on_event = None
|
|
4129
|
+
self._client.on_text_segment = None
|
|
3994
4130
|
self._client.on_token = None
|
|
3995
4131
|
self._client.on_activity = None
|
|
3996
4132
|
|
|
@@ -51,7 +51,12 @@ class ACPFileCredentialLifecycle(Protocol):
|
|
|
51
51
|
secret_name: str
|
|
52
52
|
path: Path | None
|
|
53
53
|
|
|
54
|
-
def materialize(
|
|
54
|
+
def materialize(
|
|
55
|
+
self,
|
|
56
|
+
registry: SecretRegistry,
|
|
57
|
+
env: dict[str, str],
|
|
58
|
+
home: Path | None = None,
|
|
59
|
+
) -> None: ...
|
|
55
60
|
|
|
56
61
|
def track_current(self) -> None: ...
|
|
57
62
|
|
|
@@ -114,6 +119,7 @@ class _CodexAuthLifecycle:
|
|
|
114
119
|
self.run_async = run_async
|
|
115
120
|
self.path: Path | None = None
|
|
116
121
|
self._runtime_dir: Path | None = None
|
|
122
|
+
self._home_link: Path | None = None
|
|
117
123
|
self._registry: SecretRegistry | None = None
|
|
118
124
|
self._expected_version: str | None = None
|
|
119
125
|
self._local_digest: str | None = None
|
|
@@ -126,7 +132,20 @@ class _CodexAuthLifecycle:
|
|
|
126
132
|
self._monitor: threading.Thread | None = None
|
|
127
133
|
self._closed = False
|
|
128
134
|
|
|
129
|
-
def materialize(
|
|
135
|
+
def materialize(
|
|
136
|
+
self,
|
|
137
|
+
registry: SecretRegistry,
|
|
138
|
+
env: dict[str, str],
|
|
139
|
+
home: Path | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Write the credential to a private temporary file for the CLI.
|
|
142
|
+
|
|
143
|
+
With ``home`` (the conversation's durable Codex directory), ``CODEX_HOME``
|
|
144
|
+
points there so the CLI's threads and state survive a restart, and
|
|
145
|
+
``home/auth.json`` is a symlink to the temporary file. codex resolves that
|
|
146
|
+
link and replaces its target when it refreshes the login, so the
|
|
147
|
+
credential itself never lands in ``home``.
|
|
148
|
+
"""
|
|
130
149
|
resolved = self._load()
|
|
131
150
|
if not is_valid_codex_auth(resolved.value):
|
|
132
151
|
raise CredentialNeedsReauthentication(
|
|
@@ -135,8 +154,13 @@ class _CodexAuthLifecycle:
|
|
|
135
154
|
runtime_dir = Path(tempfile.mkdtemp(prefix="openhands-codex-"))
|
|
136
155
|
runtime_dir.chmod(0o700)
|
|
137
156
|
path = runtime_dir / "auth.json"
|
|
157
|
+
codex_home = runtime_dir
|
|
138
158
|
try:
|
|
139
159
|
write_secret_file(path, resolved.value)
|
|
160
|
+
if home is not None:
|
|
161
|
+
link = self._link_into_home(home, path)
|
|
162
|
+
if link is not None:
|
|
163
|
+
path, codex_home = link, home
|
|
140
164
|
except BaseException:
|
|
141
165
|
shutil.rmtree(runtime_dir, ignore_errors=True)
|
|
142
166
|
raise
|
|
@@ -146,7 +170,7 @@ class _CodexAuthLifecycle:
|
|
|
146
170
|
self._expected_version = resolved.version
|
|
147
171
|
self._local_digest = self._digest(resolved.value)
|
|
148
172
|
self._track(resolved.value)
|
|
149
|
-
env["CODEX_HOME"] = str(
|
|
173
|
+
env["CODEX_HOME"] = str(codex_home)
|
|
150
174
|
monitor = threading.Thread(
|
|
151
175
|
target=self._monitor_loop,
|
|
152
176
|
name="codex-credential-monitor",
|
|
@@ -164,6 +188,26 @@ class _CodexAuthLifecycle:
|
|
|
164
188
|
extra={"credential": self.secret_name},
|
|
165
189
|
)
|
|
166
190
|
|
|
191
|
+
def _link_into_home(self, home: Path, target: Path) -> Path | None:
|
|
192
|
+
link = home / "auth.json"
|
|
193
|
+
try:
|
|
194
|
+
home.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
195
|
+
# A real file here is a stale copy (older releases, or a CLI that
|
|
196
|
+
# replaced the link): the binding's value supersedes it.
|
|
197
|
+
link.unlink(missing_ok=True)
|
|
198
|
+
link.symlink_to(target)
|
|
199
|
+
except OSError:
|
|
200
|
+
# No symlinks (e.g. Windows without the privilege): keep the CLI's
|
|
201
|
+
# state in the temporary directory, as before.
|
|
202
|
+
logger.warning(
|
|
203
|
+
"credential_binding_home_link_failed",
|
|
204
|
+
extra={"credential": self.secret_name},
|
|
205
|
+
exc_info=True,
|
|
206
|
+
)
|
|
207
|
+
return None
|
|
208
|
+
self._home_link = link
|
|
209
|
+
return link
|
|
210
|
+
|
|
167
211
|
def track_current(self) -> None:
|
|
168
212
|
try:
|
|
169
213
|
self._raise_sticky_error()
|
|
@@ -230,12 +274,22 @@ class _CodexAuthLifecycle:
|
|
|
230
274
|
self._cleanup_runtime()
|
|
231
275
|
|
|
232
276
|
def _cleanup_runtime(self) -> None:
|
|
277
|
+
home_link = self._home_link
|
|
278
|
+
if home_link is not None:
|
|
279
|
+
try:
|
|
280
|
+
home_link.unlink(missing_ok=True)
|
|
281
|
+
except OSError:
|
|
282
|
+
logger.warning(
|
|
283
|
+
"credential_binding_home_link_cleanup_failed",
|
|
284
|
+
extra={"credential": self.secret_name},
|
|
285
|
+
)
|
|
233
286
|
runtime_dir = self._runtime_dir
|
|
234
287
|
if runtime_dir is not None:
|
|
235
288
|
shutil.rmtree(runtime_dir, ignore_errors=True)
|
|
236
289
|
with self._lock:
|
|
237
290
|
self.path = None
|
|
238
291
|
self._runtime_dir = None
|
|
292
|
+
self._home_link = None
|
|
239
293
|
self._registry = None
|
|
240
294
|
self._monitor = None
|
|
241
295
|
self._closed = True
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Expose skills to any ACP agent as files plus a catalog that points at them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from html import escape
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from openhands.sdk.skills import Skill
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _description(skill: Skill) -> str:
|
|
15
|
+
if skill.description:
|
|
16
|
+
return skill.description
|
|
17
|
+
return next(
|
|
18
|
+
(line.strip() for line in skill.content.splitlines() if line.strip()),
|
|
19
|
+
skill.name,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _directory_name(name: str, taken: set[str]) -> str:
|
|
24
|
+
base = re.sub(r"[^A-Za-z0-9._-]+", "-", name).strip(".-") or "skill"
|
|
25
|
+
candidate, suffix = base, 2
|
|
26
|
+
while candidate.lower() in taken:
|
|
27
|
+
candidate, suffix = f"{base}-{suffix}", suffix + 1
|
|
28
|
+
taken.add(candidate.lower())
|
|
29
|
+
return candidate
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_skill_files(root: Path, skills: Sequence[Skill]) -> dict[str, Path]:
|
|
33
|
+
"""Write each skill to ``root/<dir>/SKILL.md``; return its path by skill name."""
|
|
34
|
+
locations: dict[str, Path] = {}
|
|
35
|
+
taken: set[str] = set()
|
|
36
|
+
for skill in skills:
|
|
37
|
+
skill_dir = root / _directory_name(skill.name, taken)
|
|
38
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
path = skill_dir / "SKILL.md"
|
|
40
|
+
name = json.dumps(skill.name, ensure_ascii=False)
|
|
41
|
+
description = json.dumps(_description(skill)[:1000], ensure_ascii=False)
|
|
42
|
+
path.write_text(
|
|
43
|
+
f"---\nname: {name}\ndescription: {description}\n---\n\n{skill.content}\n",
|
|
44
|
+
encoding="utf-8",
|
|
45
|
+
)
|
|
46
|
+
locations[skill.name] = path
|
|
47
|
+
return locations
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def render_skill_catalog(skills: Sequence[Skill], locations: dict[str, Path]) -> str:
|
|
51
|
+
entries = [
|
|
52
|
+
" <skill>\n"
|
|
53
|
+
f" <name>{escape(skill.name)}</name>\n"
|
|
54
|
+
f" <description>{escape(_description(skill)[:1024])}</description>\n"
|
|
55
|
+
f" <location>{escape(str(locations[skill.name]))}</location>\n"
|
|
56
|
+
" </skill>"
|
|
57
|
+
for skill in skills
|
|
58
|
+
if skill.name in locations
|
|
59
|
+
]
|
|
60
|
+
return (
|
|
61
|
+
"<SKILLS>\n"
|
|
62
|
+
"The following skills are available. When one is relevant to the task, "
|
|
63
|
+
"read its SKILL.md at the given location with your file-reading tool "
|
|
64
|
+
"and follow its instructions.\n\n"
|
|
65
|
+
"<available_skills>\n" + "\n".join(entries) + "\n</available_skills>\n"
|
|
66
|
+
"</SKILLS>"
|
|
67
|
+
)
|
|
@@ -51,7 +51,9 @@ from openhands.sdk.event.condenser import (
|
|
|
51
51
|
CondensationRequest,
|
|
52
52
|
)
|
|
53
53
|
from openhands.sdk.event.error_classification import AGENT_OUTCOME
|
|
54
|
+
from openhands.sdk.event.llm_convertible.observation import ToolValidationError
|
|
54
55
|
from openhands.sdk.event.security import SecurityAnalysisEvent
|
|
56
|
+
from openhands.sdk.event.types import ROOT_PARENT_ID, EventID
|
|
55
57
|
from openhands.sdk.llm import (
|
|
56
58
|
LLM,
|
|
57
59
|
ImageContent,
|
|
@@ -561,12 +563,25 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
561
563
|
)
|
|
562
564
|
return
|
|
563
565
|
|
|
566
|
+
# A damaged event log can lose the SystemPromptEvent's file. The first
|
|
567
|
+
# surviving event still names it as its parent, or names the copy that an
|
|
568
|
+
# earlier resume re-created under the same id.
|
|
569
|
+
restored_prompt_id: EventID | None = None
|
|
570
|
+
if has_user_message:
|
|
571
|
+
parent_id = state.events[0].parent_id
|
|
572
|
+
if parent_id is not None and parent_id in state.events:
|
|
573
|
+
parent = state.events[state.events.get_index(parent_id)]
|
|
574
|
+
if isinstance(parent, SystemPromptEvent):
|
|
575
|
+
return
|
|
576
|
+
elif parent_id not in (None, ROOT_PARENT_ID):
|
|
577
|
+
restored_prompt_id = parent_id
|
|
578
|
+
|
|
564
579
|
# Assert: A user message should never appear before the system prompt.
|
|
565
580
|
#
|
|
566
581
|
# NOTE: This is a best-effort check based on the first few events only.
|
|
567
582
|
# Remote conversations can include a ConversationStateUpdateEvent near the
|
|
568
583
|
# start, so we scan a small prefix window.
|
|
569
|
-
if has_user_message:
|
|
584
|
+
if has_user_message and restored_prompt_id is None:
|
|
570
585
|
event_types = [type(e).__name__ for e in prefix_events]
|
|
571
586
|
logger.error(
|
|
572
587
|
f"init_state: User message found in prefix before SystemPromptEvent! "
|
|
@@ -598,7 +613,29 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
598
613
|
if dynamic_context
|
|
599
614
|
else None,
|
|
600
615
|
)
|
|
601
|
-
|
|
616
|
+
if restored_prompt_id is None:
|
|
617
|
+
on_event(event)
|
|
618
|
+
return
|
|
619
|
+
|
|
620
|
+
# Re-create it as an explicit root under its old id, so the surviving
|
|
621
|
+
# history hangs off it again; appending must not move HEAD onto it.
|
|
622
|
+
logger.warning(
|
|
623
|
+
"SystemPromptEvent '%s' is missing from the event log (its file was "
|
|
624
|
+
"lost); re-creating it as the root of the surviving history. "
|
|
625
|
+
"conversation_id=%s",
|
|
626
|
+
restored_prompt_id,
|
|
627
|
+
state.id,
|
|
628
|
+
)
|
|
629
|
+
head = state.active_branch(limit=1)
|
|
630
|
+
head_is_empty = state.head_is_empty
|
|
631
|
+
on_event(
|
|
632
|
+
event.model_copy(
|
|
633
|
+
update={"id": restored_prompt_id, "parent_id": ROOT_PARENT_ID}
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
state.leaf_event_id = head[-1].id if head else None
|
|
637
|
+
state.head_is_empty = head_is_empty
|
|
638
|
+
state.rebuild_view()
|
|
602
639
|
|
|
603
640
|
def get_dynamic_context(self, state: ConversationState) -> str | None:
|
|
604
641
|
"""Get dynamic context for the system prompt, including secrets from state.
|
|
@@ -1286,6 +1323,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1286
1323
|
thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
|
|
1287
1324
|
responses_reasoning_item: ReasoningItemModel | None = None,
|
|
1288
1325
|
stream: StreamContext | None = None,
|
|
1326
|
+
validation_errors: list[ToolValidationError] | None = None,
|
|
1289
1327
|
) -> None:
|
|
1290
1328
|
try:
|
|
1291
1329
|
json.loads(tool_call.arguments)
|
|
@@ -1325,6 +1363,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1325
1363
|
tool_name=tool_name,
|
|
1326
1364
|
tool_call_id=tool_call.id,
|
|
1327
1365
|
classification=AGENT_OUTCOME,
|
|
1366
|
+
validation_errors=validation_errors,
|
|
1328
1367
|
)
|
|
1329
1368
|
record_tool_result(
|
|
1330
1369
|
conversation,
|
|
@@ -1453,6 +1492,18 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1453
1492
|
thinking_blocks=thinking_blocks,
|
|
1454
1493
|
responses_reasoning_item=responses_reasoning_item,
|
|
1455
1494
|
stream=stream,
|
|
1495
|
+
validation_errors=(
|
|
1496
|
+
[
|
|
1497
|
+
ToolValidationError(type=detail["type"], loc=detail["loc"])
|
|
1498
|
+
for detail in e.errors(
|
|
1499
|
+
include_url=False,
|
|
1500
|
+
include_context=False,
|
|
1501
|
+
include_input=False,
|
|
1502
|
+
)
|
|
1503
|
+
]
|
|
1504
|
+
if isinstance(e, ValidationError)
|
|
1505
|
+
else None
|
|
1506
|
+
),
|
|
1456
1507
|
)
|
|
1457
1508
|
return
|
|
1458
1509
|
|