openhands-sdk 1.44.0__tar.gz → 1.45.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.44.0 → openhands_sdk-1.45.0}/PKG-INFO +4 -4
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_agent.py +313 -41
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_file_credentials.py +19 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_tracing.py +1 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/base.py +6 -20
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/response_dispatch.py +23 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/memory.py +11 -2
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/prompt.py +5 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/static.py +30 -4
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/event_store.py +51 -10
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/events_list_base.py +2 -2
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/impl/local_conversation.py +76 -21
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/impl/remote_conversation.py +9 -2
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/secret_registry.py +54 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/state.py +12 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/manager.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/git_changes.py +6 -8
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/config.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/local.py +3 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/auth/credentials.py +8 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm_profile_store.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/mixins/fn_call_converter.py +9 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/model_features.py +4 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/verified_models.py +53 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/observability/laminar.py +16 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/discovery.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/fetch.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/__init__.py +5 -4
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/agent_plugins.py +71 -23
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/base.py +59 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/claude_code.py +9 -48
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/installed.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/source.py +6 -2
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/agent_profile.py +3 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/agent_profile_store.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/resolver.py +24 -17
- openhands_sdk-1.45.0/openhands/sdk/settings/acp_install_catalog.py +254 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/acp_providers.py +263 -47
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/model.py +3 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/fetch.py +2 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/installed.py +2 -2
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/skill.py +3 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/utils.py +4 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/load.py +14 -2
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/registry.py +6 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/tool.py +16 -9
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/command.py +21 -2
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/path.py +48 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/base.py +0 -1
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/PKG-INFO +4 -4
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/SOURCES.txt +2 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/requires.txt +3 -3
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/pyproject.toml +4 -4
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/agent.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/request.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.44.0 → openhands_sdk-1.45.0}/setup.cfg +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.45.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
|
|
7
7
|
Project-URL: Documentation, https://docs.openhands.dev/sdk
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/OpenHands/software-agent-sdk/issues
|
|
9
9
|
Requires-Python: >=3.12
|
|
10
|
-
Requires-Dist: agent-client-protocol
|
|
10
|
+
Requires-Dist: agent-client-protocol<0.11.0,>=0.10.1
|
|
11
11
|
Requires-Dist: deprecation>=2.1.0
|
|
12
12
|
Requires-Dist: fakeredis[lua]>=2.32.1
|
|
13
|
-
Requires-Dist: fastmcp>=3.
|
|
13
|
+
Requires-Dist: fastmcp>=3.2.0
|
|
14
14
|
Requires-Dist: filelock>=3.20.1
|
|
15
15
|
Requires-Dist: httpx[socks]>=0.27.0
|
|
16
16
|
Requires-Dist: jsonschema>=4.23.0
|
|
@@ -22,7 +22,7 @@ Requires-Dist: python-frontmatter>=1.1.0
|
|
|
22
22
|
Requires-Dist: python-json-logger>=3.3.0
|
|
23
23
|
Requires-Dist: tenacity>=9.1.2
|
|
24
24
|
Requires-Dist: websockets>=12
|
|
25
|
-
Requires-Dist: lmnr<0.8.0,>=0.7.
|
|
25
|
+
Requires-Dist: lmnr<0.8.0,>=0.7.60
|
|
26
26
|
Requires-Dist: tree-sitter>=0.25
|
|
27
27
|
Requires-Dist: tree-sitter-bash>=0.25
|
|
28
28
|
Provides-Extra: boto3
|
|
@@ -27,7 +27,14 @@ import threading
|
|
|
27
27
|
import time
|
|
28
28
|
import uuid
|
|
29
29
|
import weakref
|
|
30
|
-
from collections.abc import
|
|
30
|
+
from collections.abc import (
|
|
31
|
+
Awaitable,
|
|
32
|
+
Callable,
|
|
33
|
+
Collection,
|
|
34
|
+
Generator,
|
|
35
|
+
Iterable,
|
|
36
|
+
Sequence,
|
|
37
|
+
)
|
|
31
38
|
from concurrent.futures import Future
|
|
32
39
|
from pathlib import Path
|
|
33
40
|
from typing import TYPE_CHECKING, Any, Final, Literal, NamedTuple
|
|
@@ -69,6 +76,7 @@ from openhands.sdk.agent.acp_file_credentials import (
|
|
|
69
76
|
codex_auth_file,
|
|
70
77
|
codex_auth_file_is_chatgpt,
|
|
71
78
|
create_file_credential_lifecycle,
|
|
79
|
+
file_credential_looks_usable,
|
|
72
80
|
write_secret_file,
|
|
73
81
|
)
|
|
74
82
|
from openhands.sdk.agent.acp_models import ACPModelInfo
|
|
@@ -94,11 +102,15 @@ from openhands.sdk.logger import get_logger
|
|
|
94
102
|
from openhands.sdk.mcp.config import MCPServer
|
|
95
103
|
from openhands.sdk.observability.laminar import maybe_init_laminar, observe
|
|
96
104
|
from openhands.sdk.settings.acp_providers import (
|
|
105
|
+
ACP_PROVIDERS,
|
|
106
|
+
ACPEnvConflictSpec,
|
|
97
107
|
ACPFileSecretSpec,
|
|
108
|
+
ACPProviderInfo,
|
|
98
109
|
build_session_model_meta,
|
|
99
110
|
default_acp_file_secrets,
|
|
100
111
|
detect_acp_provider_by_agent_name,
|
|
101
112
|
detect_acp_provider_by_command,
|
|
113
|
+
get_acp_provider,
|
|
102
114
|
)
|
|
103
115
|
from openhands.sdk.tool import Tool # noqa: TC002
|
|
104
116
|
from openhands.sdk.tool.builtins.finish import FinishAction, FinishObservation
|
|
@@ -145,6 +157,10 @@ _ACP_CANCEL_DRAIN_TIMEOUT: float = float(
|
|
|
145
157
|
)
|
|
146
158
|
|
|
147
159
|
_ACP_AUTH_TIMEOUT: float = float(os.environ.get("ACP_AUTH_TIMEOUT", "30.0"))
|
|
160
|
+
_ACP_NPX_CACHE_WARM_TIMEOUT: float = float(
|
|
161
|
+
os.environ.get("ACP_NPX_CACHE_WARM_TIMEOUT", "300")
|
|
162
|
+
)
|
|
163
|
+
_ACP_VERSION_RE = re.compile(r"v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)")
|
|
148
164
|
|
|
149
165
|
_ACP_PROMPT_RETRY_DELAYS: tuple[float, ...] = (5.0, 15.0, 30.0) # seconds
|
|
150
166
|
|
|
@@ -170,26 +186,6 @@ _RETRIABLE_SERVER_ERROR_CODES: frozenset[int] = frozenset({-32603})
|
|
|
170
186
|
MAX_ACP_CONTENT_CHARS: int = 30_000
|
|
171
187
|
_ACP_SUBPROCESS_LOG_LINE_CHARS = 4_000
|
|
172
188
|
|
|
173
|
-
# Env vars that must be removed from the subprocess environment when a
|
|
174
|
-
# particular "dominant" env var is present.
|
|
175
|
-
#
|
|
176
|
-
# Rationale: Claude Code's subscription auth uses CLAUDE_CODE_OAUTH_TOKEN, a
|
|
177
|
-
# bearer validated against api.anthropic.com. A co-present ANTHROPIC_API_KEY
|
|
178
|
-
# would take precedence over the token (silently bypassing the subscription),
|
|
179
|
-
# and an ANTHROPIC_BASE_URL would route the bearer to a proxy that rejects it —
|
|
180
|
-
# either silently breaks the intended OAuth auth. When the OAuth token is the
|
|
181
|
-
# active credential we strip both so the subprocess authenticates with the
|
|
182
|
-
# token against api.anthropic.com.
|
|
183
|
-
#
|
|
184
|
-
# Keyed on the credential itself (CLAUDE_CODE_OAUTH_TOKEN), NOT on
|
|
185
|
-
# CLAUDE_CONFIG_DIR: the config dir is a *location* lever (data-dir isolation,
|
|
186
|
-
# #1019) that is orthogonal to which credential is active. Keying the strip on
|
|
187
|
-
# it wrongly fired during API-key isolation and missed the conflict when the
|
|
188
|
-
# token arrived via env without isolation (#3588).
|
|
189
|
-
_ENV_CONFLICT_MAP: dict[str, frozenset[str]] = {
|
|
190
|
-
"CLAUDE_CODE_OAUTH_TOKEN": frozenset({"ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"}),
|
|
191
|
-
}
|
|
192
|
-
|
|
193
189
|
# Number of trailing characters of an ACP session id retained in log lines
|
|
194
190
|
# for correlation. ACP session ids are server-issued tokens whose possession
|
|
195
191
|
# alone is enough to call ``session/load`` — they're effectively bearer
|
|
@@ -242,8 +238,9 @@ _STREAM_READER_LIMIT: int = 100 * 1024 * 1024 # 100 MiB
|
|
|
242
238
|
|
|
243
239
|
# Minimum interval between on_activity heartbeat signals (seconds).
|
|
244
240
|
# Throttled to avoid excessive calls while still keeping the idle timer
|
|
245
|
-
# well below the ~20 min runtime-api kill threshold.
|
|
246
|
-
|
|
241
|
+
# well below the ~20 min runtime-api kill threshold. Shared with the
|
|
242
|
+
# agent-server's streaming-delta heartbeat so both paths keep the same pace.
|
|
243
|
+
ACTIVITY_SIGNAL_INTERVAL: Final[float] = 30.0
|
|
247
244
|
|
|
248
245
|
# ACP tool-call statuses that represent a terminal outcome. Non-terminal
|
|
249
246
|
# statuses (``pending``, ``in_progress``) mean the call is still in flight
|
|
@@ -291,6 +288,44 @@ _AUTH_METHOD_ENV_MAP: dict[str, str] = {
|
|
|
291
288
|
_GEMINI_OAUTH_PATH = Path(".gemini") / "oauth_creds.json"
|
|
292
289
|
|
|
293
290
|
|
|
291
|
+
def _preconfigured_credentials(
|
|
292
|
+
provider: ACPProviderInfo | None,
|
|
293
|
+
specs: Sequence[ACPFileSecretSpec],
|
|
294
|
+
env: dict[str, str],
|
|
295
|
+
) -> list[str]:
|
|
296
|
+
"""Credential sources already in place, named as they were supplied.
|
|
297
|
+
|
|
298
|
+
A server whose advertised auth methods we cannot perform may still be
|
|
299
|
+
authenticated out of band — from a credential file seeded onto disk, or
|
|
300
|
+
from the provider key it reads straight out of its environment. Both make
|
|
301
|
+
"no credential is available" the wrong thing to say. Read off the registry
|
|
302
|
+
record rather than provider names, so this holds for any provider.
|
|
303
|
+
"""
|
|
304
|
+
present: list[str] = []
|
|
305
|
+
for spec in specs:
|
|
306
|
+
location = env.get(spec.env_var)
|
|
307
|
+
if not location:
|
|
308
|
+
continue
|
|
309
|
+
path = Path(location)
|
|
310
|
+
if spec.env_points_to == "dir":
|
|
311
|
+
path = path / spec.filename
|
|
312
|
+
if not path.is_file():
|
|
313
|
+
continue
|
|
314
|
+
# A file that exists but cannot authenticate is the case that most
|
|
315
|
+
# needs the warning, so presence alone is not enough where the SDK
|
|
316
|
+
# knows the format.
|
|
317
|
+
try:
|
|
318
|
+
text = path.read_text(encoding="utf-8")
|
|
319
|
+
except (OSError, UnicodeError):
|
|
320
|
+
continue
|
|
321
|
+
if file_credential_looks_usable(spec.secret_name, text):
|
|
322
|
+
present.append(spec.secret_name)
|
|
323
|
+
api_key_var = provider.api_key_env_var if provider is not None else None
|
|
324
|
+
if api_key_var and env.get(api_key_var):
|
|
325
|
+
present.append(api_key_var)
|
|
326
|
+
return present
|
|
327
|
+
|
|
328
|
+
|
|
294
329
|
def _select_auth_method(
|
|
295
330
|
auth_methods: list[Any],
|
|
296
331
|
env: dict[str, str],
|
|
@@ -336,7 +371,11 @@ def _select_auth_method(
|
|
|
336
371
|
return None
|
|
337
372
|
|
|
338
373
|
|
|
339
|
-
def _auth_selection_failure_reason(
|
|
374
|
+
def _auth_selection_failure_reason(
|
|
375
|
+
auth_methods: list[Any],
|
|
376
|
+
env: dict[str, str],
|
|
377
|
+
provider: ACPProviderInfo | None = None,
|
|
378
|
+
) -> str:
|
|
340
379
|
method_ids = {m.id for m in auth_methods}
|
|
341
380
|
reasons: list[str] = []
|
|
342
381
|
if "chat-gpt" in method_ids:
|
|
@@ -349,18 +388,113 @@ def _auth_selection_failure_reason(auth_methods: list[Any], env: dict[str, str])
|
|
|
349
388
|
env.get(name) for name in ("CODEX_API_KEY", "OPENAI_API_KEY")
|
|
350
389
|
):
|
|
351
390
|
reasons.append("CODEX_API_KEY and OPENAI_API_KEY are unset")
|
|
391
|
+
if provider is not None and provider.api_key_env_var:
|
|
392
|
+
if not env.get(provider.api_key_env_var):
|
|
393
|
+
reasons.append(f"{provider.api_key_env_var} is unset")
|
|
394
|
+
# ``type: "terminal"`` marks a login only an interactive TTY can complete,
|
|
395
|
+
# which the runtime does not have. Named so the log says why the method was
|
|
396
|
+
# never a candidate rather than implying a missing credential.
|
|
397
|
+
terminal_ids = sorted(
|
|
398
|
+
m.id for m in auth_methods if getattr(m, "type", None) == "terminal"
|
|
399
|
+
)
|
|
400
|
+
if terminal_ids:
|
|
401
|
+
reasons.append(
|
|
402
|
+
f"{', '.join(terminal_ids)} needs an interactive terminal, which the "
|
|
403
|
+
"headless runtime cannot provide"
|
|
404
|
+
)
|
|
352
405
|
return "; ".join(reasons) or "no supported credential source is available"
|
|
353
406
|
|
|
354
407
|
|
|
355
|
-
def _warn_auth_selection_failure(
|
|
408
|
+
def _warn_auth_selection_failure(
|
|
409
|
+
auth_methods: list[Any],
|
|
410
|
+
env: dict[str, str],
|
|
411
|
+
provider: ACPProviderInfo | None = None,
|
|
412
|
+
) -> None:
|
|
356
413
|
logger.warning(
|
|
357
414
|
"ACP server offers auth methods %s but no matching credential is available "
|
|
358
415
|
"(%s) — session creation may fail",
|
|
359
416
|
[m.id for m in auth_methods],
|
|
360
|
-
_auth_selection_failure_reason(auth_methods, env),
|
|
417
|
+
_auth_selection_failure_reason(auth_methods, env, provider),
|
|
361
418
|
)
|
|
362
419
|
|
|
363
420
|
|
|
421
|
+
def _log_acp_provider_version(agent_name: str, agent_version: str) -> None:
|
|
422
|
+
try:
|
|
423
|
+
provider = detect_acp_provider_by_agent_name(agent_name)
|
|
424
|
+
if provider is None:
|
|
425
|
+
return
|
|
426
|
+
pinned_version = None
|
|
427
|
+
for command_part in provider.default_command:
|
|
428
|
+
package, separator, version = command_part.rpartition("@")
|
|
429
|
+
if separator and package:
|
|
430
|
+
pinned_version = version
|
|
431
|
+
break
|
|
432
|
+
reported_version = next(
|
|
433
|
+
(
|
|
434
|
+
match.group(1)
|
|
435
|
+
for value in (agent_version, agent_name)
|
|
436
|
+
if (match := _ACP_VERSION_RE.search(value)) is not None
|
|
437
|
+
),
|
|
438
|
+
None,
|
|
439
|
+
)
|
|
440
|
+
logger.info(
|
|
441
|
+
"ACP provider version: provider=%s, pinned_version=%r, "
|
|
442
|
+
"agent_name=%r, agent_version=%r, reported_version=%r",
|
|
443
|
+
provider.key,
|
|
444
|
+
pinned_version,
|
|
445
|
+
agent_name,
|
|
446
|
+
agent_version,
|
|
447
|
+
reported_version,
|
|
448
|
+
)
|
|
449
|
+
if reported_version is None:
|
|
450
|
+
logger.warning(
|
|
451
|
+
"Could not parse ACP provider version: provider=%s, "
|
|
452
|
+
"pinned_version=%r, agent_name=%r, agent_version=%r",
|
|
453
|
+
provider.key,
|
|
454
|
+
pinned_version,
|
|
455
|
+
agent_name,
|
|
456
|
+
agent_version,
|
|
457
|
+
)
|
|
458
|
+
elif pinned_version is not None and reported_version != pinned_version:
|
|
459
|
+
logger.warning(
|
|
460
|
+
"ACP provider version mismatch: provider=%s, pinned_version=%r, "
|
|
461
|
+
"reported_version=%r; provider was probably installed at runtime "
|
|
462
|
+
"via the npx fallback rather than preinstalled in the image",
|
|
463
|
+
provider.key,
|
|
464
|
+
pinned_version,
|
|
465
|
+
reported_version,
|
|
466
|
+
)
|
|
467
|
+
except Exception:
|
|
468
|
+
logger.warning(
|
|
469
|
+
"Could not verify ACP provider version: agent_name=%r, agent_version=%r",
|
|
470
|
+
agent_name,
|
|
471
|
+
agent_version,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _npx_packages(command: list[str]) -> list[str]:
|
|
476
|
+
"""Pinned npm specs an ``npx`` command installs, for the cache warm.
|
|
477
|
+
|
|
478
|
+
``--package=<spec>`` flags when present — pi pins its ACP adapter and the
|
|
479
|
+
engine it spawns separately, and its positional argument is the bare binary
|
|
480
|
+
name, which would warm an unpinned package. Otherwise the first positional,
|
|
481
|
+
which is the package for the single-package providers.
|
|
482
|
+
"""
|
|
483
|
+
if not command or command[0] != "npx":
|
|
484
|
+
return []
|
|
485
|
+
pinned = [
|
|
486
|
+
arg.removeprefix("--package=")
|
|
487
|
+
for arg in command[1:]
|
|
488
|
+
if arg.startswith("--package=")
|
|
489
|
+
]
|
|
490
|
+
if pinned:
|
|
491
|
+
return pinned
|
|
492
|
+
for arg in command[1:]:
|
|
493
|
+
if not arg.startswith("-"):
|
|
494
|
+
return [arg]
|
|
495
|
+
return []
|
|
496
|
+
|
|
497
|
+
|
|
364
498
|
def _with_codex_base_url(
|
|
365
499
|
command: str, args: list[str], env: dict[str, str]
|
|
366
500
|
) -> dict[str, str]:
|
|
@@ -1429,13 +1563,13 @@ class _OpenHandsACPBridge:
|
|
|
1429
1563
|
the server's idle_time grows unboundedly and the runtime-api kills
|
|
1430
1564
|
the pod (default idle threshold ~20 min).
|
|
1431
1565
|
|
|
1432
|
-
Throttled to at most once per
|
|
1566
|
+
Throttled to at most once per ACTIVITY_SIGNAL_INTERVAL seconds to
|
|
1433
1567
|
avoid excessive overhead on chatty ACP servers.
|
|
1434
1568
|
"""
|
|
1435
1569
|
if self.on_activity is None:
|
|
1436
1570
|
return
|
|
1437
1571
|
now = time.monotonic()
|
|
1438
|
-
if now - self._last_activity_signal >=
|
|
1572
|
+
if now - self._last_activity_signal >= ACTIVITY_SIGNAL_INTERVAL:
|
|
1439
1573
|
self._last_activity_signal = now
|
|
1440
1574
|
try:
|
|
1441
1575
|
self.on_activity()
|
|
@@ -1552,8 +1686,8 @@ class ACPAgent(AgentBase):
|
|
|
1552
1686
|
default=None,
|
|
1553
1687
|
description=(
|
|
1554
1688
|
"Provider registry key identifying which ACP CLI this agent runs "
|
|
1555
|
-
"(
|
|
1556
|
-
"
|
|
1689
|
+
"(any ACP_PROVIDERS key, or 'custom'); None when the agent is "
|
|
1690
|
+
"built directly rather than via ACPAgentSettings. Set by "
|
|
1557
1691
|
"ACPAgentSettings.create_agent() from ACPAgentSettings.acp_server so "
|
|
1558
1692
|
"the authoritative key survives onto the agent — and thus onto "
|
|
1559
1693
|
"ConversationInfo.agent — because the launch command in acp_command "
|
|
@@ -1572,7 +1706,8 @@ class ACPAgent(AgentBase):
|
|
|
1572
1706
|
"Session mode ID to set after creating a session. "
|
|
1573
1707
|
"If None (default), auto-detected from the ACP server type: "
|
|
1574
1708
|
"'bypassPermissions' for claude-agent-acp, "
|
|
1575
|
-
"'agent-full-access' for codex-acp
|
|
1709
|
+
"'agent-full-access' for codex-acp; a provider with no such mode "
|
|
1710
|
+
"(pi-acp) skips the call."
|
|
1576
1711
|
),
|
|
1577
1712
|
)
|
|
1578
1713
|
acp_prompt_timeout: float = Field(
|
|
@@ -1699,6 +1834,21 @@ class ACPAgent(AgentBase):
|
|
|
1699
1834
|
),
|
|
1700
1835
|
)
|
|
1701
1836
|
|
|
1837
|
+
@field_validator("agent_context")
|
|
1838
|
+
@classmethod
|
|
1839
|
+
def _drop_project_skills(cls, value: AgentContext | None) -> AgentContext | None:
|
|
1840
|
+
"""Clear ``load_project_skills`` — ACP CLIs read the repo themselves.
|
|
1841
|
+
|
|
1842
|
+
Claude Code, Codex and Gemini already ingest ``AGENTS.md`` / ``CLAUDE.md``
|
|
1843
|
+
and their own project skills from the session cwd, so loading them here
|
|
1844
|
+
too would put that content in the prompt twice. Normalised rather than
|
|
1845
|
+
rejected: callers legitimately set the flag on a shared context they also
|
|
1846
|
+
use for OpenHands agents (#4019).
|
|
1847
|
+
"""
|
|
1848
|
+
if value is None or not value.load_project_skills:
|
|
1849
|
+
return value
|
|
1850
|
+
return value.model_copy(update={"load_project_skills": False})
|
|
1851
|
+
|
|
1702
1852
|
def model_post_init(self, __context: object) -> None:
|
|
1703
1853
|
super().model_post_init(__context)
|
|
1704
1854
|
# Propagate the actual model name to the sentinel LLM and its
|
|
@@ -2325,6 +2475,59 @@ class ACPAgent(AgentBase):
|
|
|
2325
2475
|
root = Path(state.workspace.working_dir) / ".openhands" / "acp" / subdir
|
|
2326
2476
|
return Path(os.path.abspath(root))
|
|
2327
2477
|
|
|
2478
|
+
def _acp_npm_cache_dir(self, state: ConversationState) -> Path:
|
|
2479
|
+
if state.persistence_dir:
|
|
2480
|
+
root = Path(state.persistence_dir).parent
|
|
2481
|
+
else:
|
|
2482
|
+
root = Path(state.workspace.working_dir) / ".openhands"
|
|
2483
|
+
cache_dir = root / "npm-cache"
|
|
2484
|
+
cache_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
2485
|
+
return Path(os.path.abspath(cache_dir))
|
|
2486
|
+
|
|
2487
|
+
async def _warm_npx_cache(
|
|
2488
|
+
self,
|
|
2489
|
+
packages: Sequence[str],
|
|
2490
|
+
provider_key: str,
|
|
2491
|
+
env: dict[str, str],
|
|
2492
|
+
cwd: str,
|
|
2493
|
+
) -> None:
|
|
2494
|
+
logger.info(
|
|
2495
|
+
"Warming ACP provider npx cache: provider=%s, packages=%s; "
|
|
2496
|
+
"first use may download the provider before session startup",
|
|
2497
|
+
provider_key,
|
|
2498
|
+
list(packages),
|
|
2499
|
+
)
|
|
2500
|
+
package_args = [arg for package in packages for arg in ("--package", package)]
|
|
2501
|
+
process = await asyncio.create_subprocess_exec(
|
|
2502
|
+
"npx",
|
|
2503
|
+
"--yes",
|
|
2504
|
+
"--prefer-offline",
|
|
2505
|
+
*package_args,
|
|
2506
|
+
"--",
|
|
2507
|
+
"node",
|
|
2508
|
+
"-e",
|
|
2509
|
+
"",
|
|
2510
|
+
cwd=cwd,
|
|
2511
|
+
env=env,
|
|
2512
|
+
stdin=asyncio.subprocess.DEVNULL,
|
|
2513
|
+
stdout=asyncio.subprocess.DEVNULL,
|
|
2514
|
+
stderr=asyncio.subprocess.DEVNULL,
|
|
2515
|
+
)
|
|
2516
|
+
try:
|
|
2517
|
+
await asyncio.wait_for(process.wait(), timeout=_ACP_NPX_CACHE_WARM_TIMEOUT)
|
|
2518
|
+
except TimeoutError as exc:
|
|
2519
|
+
process.kill()
|
|
2520
|
+
await process.wait()
|
|
2521
|
+
raise RuntimeError(
|
|
2522
|
+
"ACP provider cache warm timed out after "
|
|
2523
|
+
f"{_ACP_NPX_CACHE_WARM_TIMEOUT:.0f}s for {provider_key}"
|
|
2524
|
+
) from exc
|
|
2525
|
+
if process.returncode:
|
|
2526
|
+
raise RuntimeError(
|
|
2527
|
+
f"ACP provider cache warm failed for {provider_key} "
|
|
2528
|
+
f"(exit code {process.returncode})"
|
|
2529
|
+
)
|
|
2530
|
+
|
|
2328
2531
|
def _isolate_acp_data_dir(
|
|
2329
2532
|
self, state: ConversationState, env: dict[str, str]
|
|
2330
2533
|
) -> None:
|
|
@@ -2351,8 +2554,9 @@ class ACPAgent(AgentBase):
|
|
|
2351
2554
|
``ANTHROPIC_API_KEY`` — API-key Claude gets the same per-conversation
|
|
2352
2555
|
isolation (and pause/resume continuity) as OAuth Claude (#3588).
|
|
2353
2556
|
|
|
2354
|
-
``HOME`` (
|
|
2355
|
-
ignores ``XDG
|
|
2557
|
+
``HOME`` (the only lever for gemini-cli, which hard-codes ``~/.gemini``
|
|
2558
|
+
and ignores ``XDG``, and for pi-acp, whose session map is hard-coded to
|
|
2559
|
+
``~/.pi/pi-acp``) has a wider blast radius than the surgical
|
|
2356
2560
|
``CODEX_HOME`` / ``CLAUDE_CONFIG_DIR``: it also relocates the home dir
|
|
2357
2561
|
seen by anything the CLI subprocess itself spawns (``git``, ``npm``,
|
|
2358
2562
|
``node``, shells — e.g. ``~/.gitconfig``, ``~/.npmrc``, the npm cache).
|
|
@@ -2372,6 +2576,42 @@ class ACPAgent(AgentBase):
|
|
|
2372
2576
|
data_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
2373
2577
|
env[env_var] = str(data_dir)
|
|
2374
2578
|
|
|
2579
|
+
def _resolved_provider(self) -> ACPProviderInfo | None:
|
|
2580
|
+
"""The provider this agent runs, preferring the authoritative key.
|
|
2581
|
+
|
|
2582
|
+
``acp_server`` is set by ``ACPAgentSettings.create_agent()`` and is the
|
|
2583
|
+
only identity that survives a custom or aliased ``acp_command``; the
|
|
2584
|
+
command is the fallback for an agent constructed directly.
|
|
2585
|
+
"""
|
|
2586
|
+
return get_acp_provider(
|
|
2587
|
+
self.acp_server or ""
|
|
2588
|
+
) or detect_acp_provider_by_command(self.acp_command)
|
|
2589
|
+
|
|
2590
|
+
def _strip_conflicting_env(self, env: dict[str, str]) -> None:
|
|
2591
|
+
"""Remove env vars that would defeat this provider's own credential.
|
|
2592
|
+
|
|
2593
|
+
Scoped to the resolved provider's :attr:`ACPProviderInfo.env_conflicts`:
|
|
2594
|
+
the same variable is another provider's *credential* (a provider whose
|
|
2595
|
+
``api_key_env_var`` is ``ANTHROPIC_API_KEY``), and stripping it there
|
|
2596
|
+
leaves it with nothing to authenticate with.
|
|
2597
|
+
|
|
2598
|
+
An unrecognised server keeps the conservative union: without an identity
|
|
2599
|
+
we cannot tell whose credential a dominant variable belongs to, and a
|
|
2600
|
+
directly-constructed ``ACPAgent`` running Claude Code is the case the
|
|
2601
|
+
rule was written for (#3588).
|
|
2602
|
+
"""
|
|
2603
|
+
provider = self._resolved_provider()
|
|
2604
|
+
if provider is not None:
|
|
2605
|
+
specs: tuple[ACPEnvConflictSpec, ...] = provider.env_conflicts
|
|
2606
|
+
else:
|
|
2607
|
+
specs = tuple(
|
|
2608
|
+
spec for info in ACP_PROVIDERS.values() for spec in info.env_conflicts
|
|
2609
|
+
)
|
|
2610
|
+
for spec in specs:
|
|
2611
|
+
if spec.dominant in env:
|
|
2612
|
+
for name in spec.strip:
|
|
2613
|
+
env.pop(name, None)
|
|
2614
|
+
|
|
2375
2615
|
def _materialise_file_secrets(
|
|
2376
2616
|
self, state: ConversationState, env: dict[str, str]
|
|
2377
2617
|
) -> None:
|
|
@@ -2634,13 +2874,7 @@ class ACPAgent(AgentBase):
|
|
|
2634
2874
|
# Strip CLAUDECODE so nested Claude Code instances don't refuse to start
|
|
2635
2875
|
env.pop("CLAUDECODE", None)
|
|
2636
2876
|
|
|
2637
|
-
|
|
2638
|
-
# CLAUDE_CODE_OAUTH_TOKEN must not coexist with ANTHROPIC_API_KEY (which
|
|
2639
|
-
# takes precedence) or ANTHROPIC_BASE_URL (proxies the bearer). See #3588.
|
|
2640
|
-
for dominant, conflicts in _ENV_CONFLICT_MAP.items():
|
|
2641
|
-
if dominant in env:
|
|
2642
|
-
for conflict in conflicts:
|
|
2643
|
-
env.pop(conflict, None)
|
|
2877
|
+
self._strip_conflicting_env(env)
|
|
2644
2878
|
|
|
2645
2879
|
command = self.acp_command[0]
|
|
2646
2880
|
args = list(self.acp_command[1:]) + list(self.acp_args)
|
|
@@ -2650,6 +2884,22 @@ class ACPAgent(AgentBase):
|
|
|
2650
2884
|
env = _with_codex_base_url(command, args, env)
|
|
2651
2885
|
|
|
2652
2886
|
working_dir = str(state.workspace.working_dir)
|
|
2887
|
+
provider = detect_acp_provider_by_command(self.acp_command)
|
|
2888
|
+
packages = _npx_packages(self.acp_command)
|
|
2889
|
+
if provider is not None and packages:
|
|
2890
|
+
env["npm_config_cache"] = str(self._acp_npm_cache_dir(state))
|
|
2891
|
+
try:
|
|
2892
|
+
self._executor.run_async(
|
|
2893
|
+
self._warm_npx_cache(packages, provider.key, env, working_dir),
|
|
2894
|
+
timeout=_ACP_NPX_CACHE_WARM_TIMEOUT + 5,
|
|
2895
|
+
)
|
|
2896
|
+
except Exception as error:
|
|
2897
|
+
logger.warning(
|
|
2898
|
+
"ACP provider npx cache warm failed: provider=%s; "
|
|
2899
|
+
"continuing with normal startup: %s",
|
|
2900
|
+
provider.key,
|
|
2901
|
+
error,
|
|
2902
|
+
)
|
|
2653
2903
|
|
|
2654
2904
|
# Prior ACP session id — typically survives agent-server restarts via
|
|
2655
2905
|
# ConversationState.agent_state (serialized into base_state.json).
|
|
@@ -2763,6 +3013,7 @@ class ACPAgent(AgentBase):
|
|
|
2763
3013
|
agent_name,
|
|
2764
3014
|
agent_version,
|
|
2765
3015
|
)
|
|
3016
|
+
_log_acp_provider_version(agent_name, agent_version)
|
|
2766
3017
|
|
|
2767
3018
|
# Translate any configured MCP servers into ACP protocol objects,
|
|
2768
3019
|
# gating remote (http/sse) transports on what this server advertised
|
|
@@ -2830,7 +3081,28 @@ class ACPAgent(AgentBase):
|
|
|
2830
3081
|
) from exc
|
|
2831
3082
|
await self._flush_file_credentials()
|
|
2832
3083
|
else:
|
|
2833
|
-
|
|
3084
|
+
# A server whose advertised methods we cannot perform may
|
|
3085
|
+
# still be authenticated out of band — from a seeded
|
|
3086
|
+
# credential file or the provider key it reads out of the
|
|
3087
|
+
# environment — so warning about a login we cannot do
|
|
3088
|
+
# would be noise whenever either is present.
|
|
3089
|
+
auth_provider = (
|
|
3090
|
+
self._resolved_provider()
|
|
3091
|
+
or detect_acp_provider_by_agent_name(agent_name)
|
|
3092
|
+
)
|
|
3093
|
+
configured = _preconfigured_credentials(
|
|
3094
|
+
auth_provider, self.acp_file_secrets, env
|
|
3095
|
+
)
|
|
3096
|
+
if configured:
|
|
3097
|
+
logger.info(
|
|
3098
|
+
"ACP server offers auth methods %s that cannot be "
|
|
3099
|
+
"performed here; using the already-configured "
|
|
3100
|
+
"credential(s) %s instead",
|
|
3101
|
+
[m.id for m in auth_methods],
|
|
3102
|
+
configured,
|
|
3103
|
+
)
|
|
3104
|
+
else:
|
|
3105
|
+
_warn_auth_selection_failure(auth_methods, env, auth_provider)
|
|
2834
3106
|
|
|
2835
3107
|
# Resume the prior ACP session if we have its id. If the server
|
|
2836
3108
|
# has forgotten it (state wiped, new host, etc.) fall through to
|
|
@@ -515,3 +515,22 @@ _FILE_CREDENTIAL_LIFECYCLES = {
|
|
|
515
515
|
|
|
516
516
|
def supports_file_credential_binding(secret_name: str) -> bool:
|
|
517
517
|
return secret_name in _FILE_CREDENTIAL_LIFECYCLES
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
_FILE_CREDENTIAL_VALIDATORS: dict[str, Callable[[str], bool]] = {
|
|
521
|
+
CODEX_AUTH_SECRET_NAME: is_valid_codex_auth,
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def file_credential_looks_usable(secret_name: str, text: str) -> bool:
|
|
526
|
+
"""Whether a materialised credential file is plausibly usable.
|
|
527
|
+
|
|
528
|
+
Only a *present* file can be checked here, and only for the few secrets the
|
|
529
|
+
SDK knows the shape of. An unrecognised secret gets the benefit of the
|
|
530
|
+
doubt: the CLI owns the format, and refusing to believe a credential we
|
|
531
|
+
cannot parse would warn on every provider we have not special-cased —
|
|
532
|
+
which is the noise this is meant to avoid. Returning ``True`` for unknown
|
|
533
|
+
names keeps that judgement with the provider.
|
|
534
|
+
"""
|
|
535
|
+
validator = _FILE_CREDENTIAL_VALIDATORS.get(secret_name)
|
|
536
|
+
return True if validator is None else validator(text)
|
|
@@ -25,7 +25,7 @@ logger = get_logger(__name__)
|
|
|
25
25
|
|
|
26
26
|
#: Marks a span as produced by an ACP agent rather than the native loop.
|
|
27
27
|
AGENT_KIND_METADATA_KEY = "agent_kind"
|
|
28
|
-
#: Which ACP CLI produced it
|
|
28
|
+
#: Which ACP CLI produced it: an ACP_PROVIDERS key, or 'custom'.
|
|
29
29
|
ACP_SERVER_METADATA_KEY = "acp_server"
|
|
30
30
|
#: Model the ACP CLI reported for the turn, when it reports one.
|
|
31
31
|
ACP_MODEL_METADATA_KEY = "acp_model"
|
|
@@ -43,8 +43,8 @@ from openhands.sdk.tool.builtins.vision_inspect import (
|
|
|
43
43
|
VisionInspectTool,
|
|
44
44
|
has_vision_profile_available,
|
|
45
45
|
)
|
|
46
|
-
from openhands.sdk.utils.deprecation import deprecated
|
|
47
46
|
from openhands.sdk.utils.models import DiscriminatedUnionMixin
|
|
47
|
+
from openhands.sdk.utils.path import get_user_persistence_dir
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
if TYPE_CHECKING:
|
|
@@ -58,10 +58,11 @@ logger = get_logger(__name__)
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
# -- SOUL.md loader -------------------------------------------------------
|
|
61
|
-
# SOUL.md is the agent's identity file
|
|
62
|
-
#
|
|
61
|
+
# SOUL.md is the agent's identity file, ``SOUL.md`` under the user persistence
|
|
62
|
+
# directory (~/.openhands/SOUL.md absent OH_PERSISTENCE_DIR). When present it
|
|
63
|
+
# replaces the default identity in the system prompt.
|
|
63
64
|
|
|
64
|
-
_SOUL_PATH =
|
|
65
|
+
_SOUL_PATH = get_user_persistence_dir() / "SOUL.md"
|
|
65
66
|
_DEFAULT_SOUL = (
|
|
66
67
|
"You are OpenHands agent, a helpful AI assistant that can interact"
|
|
67
68
|
" with a computer to solve tasks."
|
|
@@ -84,7 +85,7 @@ _PRESET_BY_FILENAME: dict[str, PromptPreset] = {
|
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
def _load_soul_md() -> str:
|
|
87
|
-
"""Load
|
|
88
|
+
"""Load ``SOUL.md`` from the user persistence dir, else the built-in default."""
|
|
88
89
|
try:
|
|
89
90
|
with open(_SOUL_PATH, encoding="utf-8") as f:
|
|
90
91
|
content = f.read().strip()
|
|
@@ -735,21 +736,6 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
735
736
|
|
|
736
737
|
return self
|
|
737
738
|
|
|
738
|
-
@deprecated(
|
|
739
|
-
deprecated_in="1.40.0",
|
|
740
|
-
removed_in="1.45.0",
|
|
741
|
-
details="Use model_dump(exclude_none=True) instead.",
|
|
742
|
-
)
|
|
743
|
-
def model_dump_succint(self, **kwargs):
|
|
744
|
-
"""Like model_dump, but excludes None fields by default."""
|
|
745
|
-
if "exclude_none" not in kwargs:
|
|
746
|
-
kwargs["exclude_none"] = True
|
|
747
|
-
dumped = super().model_dump(**kwargs)
|
|
748
|
-
# remove tool schema details for brevity
|
|
749
|
-
if "tools" in dumped and isinstance(dumped["tools"], dict):
|
|
750
|
-
dumped["tools"] = list(dumped["tools"].keys())
|
|
751
|
-
return dumped
|
|
752
|
-
|
|
753
739
|
def get_all_llms(self) -> Generator[LLM]:
|
|
754
740
|
"""Recursively yield unique *base-class* LLM objects reachable from `self`.
|
|
755
741
|
|
|
@@ -281,7 +281,7 @@ class ResponseDispatchMixin:
|
|
|
281
281
|
"""Create and emit a MessageEvent, running critic if configured."""
|
|
282
282
|
msg_event = MessageEvent(
|
|
283
283
|
source="agent",
|
|
284
|
-
llm_message=message,
|
|
284
|
+
llm_message=self._mask_secrets(message, conversation),
|
|
285
285
|
llm_response_id=llm_response.id,
|
|
286
286
|
)
|
|
287
287
|
if self.critic is not None and self.critic.mode == "finish_and_message":
|
|
@@ -293,6 +293,28 @@ class ResponseDispatchMixin:
|
|
|
293
293
|
on_event(msg_event)
|
|
294
294
|
return msg_event
|
|
295
295
|
|
|
296
|
+
@staticmethod
|
|
297
|
+
def _mask_secrets(message: Message, conversation: LocalConversation) -> Message:
|
|
298
|
+
"""Return ``message`` with registered secret values masked in its text.
|
|
299
|
+
|
|
300
|
+
``thinking_blocks`` and ``responses_reasoning_item`` are left alone:
|
|
301
|
+
they are signed provider payloads, and rewriting them invalidates the
|
|
302
|
+
signature replayed on the next request.
|
|
303
|
+
"""
|
|
304
|
+
mask = conversation.state.secret_registry.mask_secrets_in_output
|
|
305
|
+
reasoning = message.reasoning_content
|
|
306
|
+
return message.model_copy(
|
|
307
|
+
update={
|
|
308
|
+
"content": [
|
|
309
|
+
part.model_copy(update={"text": mask(part.text)})
|
|
310
|
+
if isinstance(part, TextContent)
|
|
311
|
+
else part
|
|
312
|
+
for part in message.content
|
|
313
|
+
],
|
|
314
|
+
"reasoning_content": mask(reasoning) if reasoning else reasoning,
|
|
315
|
+
}
|
|
316
|
+
)
|
|
317
|
+
|
|
296
318
|
def _send_corrective_nudge(self, on_event: ConversationCallbackType) -> None:
|
|
297
319
|
"""Inject corrective feedback when no tool call and no content.
|
|
298
320
|
|