openhands-sdk 1.59.0__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.59.0 → openhands_sdk-1.60.0}/PKG-INFO +4 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/__init__.py +4 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_agent.py +102 -14
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_file_credentials.py +57 -3
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/agent.py +15 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/base.py +11 -9
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/stream_context.py +35 -2
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/utils.py +36 -19
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/static.py +4 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/base.py +11 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/event_store.py +1 -5
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/exceptions.py +25 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/impl/local_conversation.py +147 -47
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/impl/remote_conversation.py +32 -15
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/request.py +1 -2
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/response_utils.py +11 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/secret_registry.py +3 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/stuck_detector.py +163 -18
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/title_utils.py +5 -11
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/taxonomy.py +4 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/observation.py +14 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/fetch.py +3 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/manager.py +14 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/git/git_changes.py +41 -92
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/git/git_commits.py +3 -2
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/git/utils.py +11 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/io/memory.py +11 -3
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/__init__.py +29 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/auth/openai.py +4 -4
- {openhands_sdk-1.59.0 → 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.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/mapping.py +7 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm.py +51 -10
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm_profile_store.py +1 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/message.py +11 -12
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/mixins/fn_call_converter.py +20 -5
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/responses_options.py +1 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/model_features.py +79 -6
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/model_info.py +32 -10
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/openhands_provider.py +16 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/retry_mixin.py +15 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/verified_models.py +16 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/tool.py +27 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/profiles/__init__.py +0 -6
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/profiles/agent_profile.py +1 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/profiles/resolver.py +5 -216
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/profiles/seed.py +1 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/grayswan/analyzer.py +7 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/settings/model.py +47 -7
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/utils.py +3 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/subagent/registry.py +24 -5
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/subagent/schema.py +4 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/__init__.py +9 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/schema.py +8 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/redact.py +3 -1
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/base.py +2 -2
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/repo.py +99 -10
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/PKG-INFO +4 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/SOURCES.txt +2 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/requires.txt +3 -3
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/pyproject.toml +4 -4
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_contracts.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_model_discovery.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_skills.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/automation/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/message_request.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/security.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/launch/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/launch/errors.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/launch/finalize.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/launch/preview.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/launch/resolve.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/_message_normalization.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/_response_stream.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/_tokenizer.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/meta_profile_store.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/retry_after.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/logger/_rich_handler.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/oauth.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/paths.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/strict_fetch.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/settings/acp_install_catalog.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/subagent/scope.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/masking.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.59.0 → 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"
|
|
@@ -26,6 +26,8 @@ from openhands.sdk.io import FileStore, LocalFileStore
|
|
|
26
26
|
from openhands.sdk.llm import (
|
|
27
27
|
LLM,
|
|
28
28
|
LLM_PROFILE_SCHEMA_VERSION,
|
|
29
|
+
DecisionModel,
|
|
30
|
+
DecisionResult,
|
|
29
31
|
FallbackStrategy,
|
|
30
32
|
ImageContent,
|
|
31
33
|
LLMProfileStore,
|
|
@@ -119,6 +121,8 @@ _print_banner(__version__)
|
|
|
119
121
|
|
|
120
122
|
__all__ = [
|
|
121
123
|
"LLM",
|
|
124
|
+
"DecisionModel",
|
|
125
|
+
"DecisionResult",
|
|
122
126
|
"LLM_PROFILE_SCHEMA_VERSION",
|
|
123
127
|
"LLMRegistry",
|
|
124
128
|
"LLMProfileStore",
|
|
@@ -19,6 +19,7 @@ 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
|
|
@@ -1342,6 +1343,14 @@ class _OpenHandsACPBridge:
|
|
|
1342
1343
|
self.accumulated_text: list[str] = []
|
|
1343
1344
|
self.accumulated_thoughts: list[str] = []
|
|
1344
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
|
|
1345
1354
|
# Emits the LLM/TOOL spans an ACP turn would otherwise never produce.
|
|
1346
1355
|
self.trace = ACPTurnTrace(acp_server=None, model_id=None)
|
|
1347
1356
|
self.on_token: Any = None # ConversationTokenCallbackType | None
|
|
@@ -1387,6 +1396,10 @@ class _OpenHandsACPBridge:
|
|
|
1387
1396
|
self.accumulated_text.clear()
|
|
1388
1397
|
self.accumulated_thoughts.clear()
|
|
1389
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
|
|
1390
1403
|
self.on_token = None
|
|
1391
1404
|
self.on_event = None
|
|
1392
1405
|
self.on_activity = None
|
|
@@ -1529,6 +1542,7 @@ class _OpenHandsACPBridge:
|
|
|
1529
1542
|
if event is not None:
|
|
1530
1543
|
event.set()
|
|
1531
1544
|
elif isinstance(update, ToolCallStart):
|
|
1545
|
+
self._emit_text_segment()
|
|
1532
1546
|
entry = {
|
|
1533
1547
|
"tool_call_id": update.tool_call_id,
|
|
1534
1548
|
"title": update.title,
|
|
@@ -1599,6 +1613,41 @@ class _OpenHandsACPBridge:
|
|
|
1599
1613
|
else:
|
|
1600
1614
|
logger.debug("ACP session update: %s", type(update).__name__)
|
|
1601
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
|
+
|
|
1602
1651
|
def _emit_tool_call_event(self, tc: dict[str, Any]) -> None:
|
|
1603
1652
|
"""Emit an ACPToolCallEvent reflecting the current state of ``tc``.
|
|
1604
1653
|
|
|
@@ -2805,16 +2854,19 @@ class ACPAgent(AgentBase):
|
|
|
2805
2854
|
if self._closed:
|
|
2806
2855
|
raise CredentialSyncError("Credential binding is closed.")
|
|
2807
2856
|
try:
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
)
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
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
|
|
2818
2870
|
except BaseException:
|
|
2819
2871
|
env.pop("CODEX_HOME", None)
|
|
2820
2872
|
lifecycle.discard()
|
|
@@ -3469,11 +3521,41 @@ class ACPAgent(AgentBase):
|
|
|
3469
3521
|
self._client.trace.start_turn(prompt)
|
|
3470
3522
|
self._client.on_token = on_token
|
|
3471
3523
|
self._client.on_event = on_event
|
|
3524
|
+
self._client.on_text_segment = functools.partial(
|
|
3525
|
+
self._emit_text_segment, on_event
|
|
3526
|
+
)
|
|
3472
3527
|
self._client.on_activity = self._on_activity
|
|
3473
3528
|
# Start the idle-timeout clock fresh for this attempt so the deadline
|
|
3474
3529
|
# is measured from the send (or retry), not from a stale value.
|
|
3475
3530
|
self._client.arm_activity_clock()
|
|
3476
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
|
+
|
|
3477
3559
|
def _cancel_inflight_tool_calls(self) -> None:
|
|
3478
3560
|
"""Emit a terminal ``failed`` ACPToolCallEvent for every tool call
|
|
3479
3561
|
in the accumulator that has not reached a terminal status yet.
|
|
@@ -3857,13 +3939,18 @@ class ACPAgent(AgentBase):
|
|
|
3857
3939
|
# gets caught before landing in the persisted event stream.
|
|
3858
3940
|
self._track_file_credentials_for_masking()
|
|
3859
3941
|
mask = state.secret_registry.mask_secrets_in_output
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
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:
|
|
3863
3948
|
response_text = "(No response from ACP server)"
|
|
3864
3949
|
|
|
3865
3950
|
self._client.trace.finish_turn(
|
|
3866
|
-
|
|
3951
|
+
mask("".join(self._client.accumulated_text)) or response_text,
|
|
3952
|
+
mask("".join(self._client.accumulated_thoughts)),
|
|
3953
|
+
self._client.accumulated_tool_calls,
|
|
3867
3954
|
)
|
|
3868
3955
|
|
|
3869
3956
|
# ACP step() boundaries are full remote assistant turns, not
|
|
@@ -4039,6 +4126,7 @@ class ACPAgent(AgentBase):
|
|
|
4039
4126
|
return
|
|
4040
4127
|
self._client.trace.abandon()
|
|
4041
4128
|
self._client.on_event = None
|
|
4129
|
+
self._client.on_text_segment = None
|
|
4042
4130
|
self._client.on_token = None
|
|
4043
4131
|
self._client.on_activity = None
|
|
4044
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
|
|
@@ -51,6 +51,7 @@ 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
|
|
55
56
|
from openhands.sdk.event.types import ROOT_PARENT_ID, EventID
|
|
56
57
|
from openhands.sdk.llm import (
|
|
@@ -1322,6 +1323,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1322
1323
|
thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
|
|
1323
1324
|
responses_reasoning_item: ReasoningItemModel | None = None,
|
|
1324
1325
|
stream: StreamContext | None = None,
|
|
1326
|
+
validation_errors: list[ToolValidationError] | None = None,
|
|
1325
1327
|
) -> None:
|
|
1326
1328
|
try:
|
|
1327
1329
|
json.loads(tool_call.arguments)
|
|
@@ -1361,6 +1363,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1361
1363
|
tool_name=tool_name,
|
|
1362
1364
|
tool_call_id=tool_call.id,
|
|
1363
1365
|
classification=AGENT_OUTCOME,
|
|
1366
|
+
validation_errors=validation_errors,
|
|
1364
1367
|
)
|
|
1365
1368
|
record_tool_result(
|
|
1366
1369
|
conversation,
|
|
@@ -1489,6 +1492,18 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1489
1492
|
thinking_blocks=thinking_blocks,
|
|
1490
1493
|
responses_reasoning_item=responses_reasoning_item,
|
|
1491
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
|
+
),
|
|
1492
1507
|
)
|
|
1493
1508
|
return
|
|
1494
1509
|
|
|
@@ -28,6 +28,9 @@ from openhands.sdk.context.prompts.section import Platform, PromptContext
|
|
|
28
28
|
from openhands.sdk.critic.base import CriticBase
|
|
29
29
|
from openhands.sdk.llm import LLM
|
|
30
30
|
from openhands.sdk.llm.utils.model_prompt_spec import get_model_prompt_spec
|
|
31
|
+
from openhands.sdk.llm.utils.openhands_provider import (
|
|
32
|
+
OPENHANDS_SYSTEM_IDENTITY,
|
|
33
|
+
)
|
|
31
34
|
from openhands.sdk.logger import get_logger
|
|
32
35
|
from openhands.sdk.mcp.client import MCPClient
|
|
33
36
|
from openhands.sdk.mcp.config import MCPServer
|
|
@@ -64,10 +67,7 @@ logger = get_logger(__name__)
|
|
|
64
67
|
# replaces the default identity in the system prompt.
|
|
65
68
|
|
|
66
69
|
_SOUL_PATH = get_user_persistence_dir() / "SOUL.md"
|
|
67
|
-
_DEFAULT_SOUL =
|
|
68
|
-
"You are OpenHands agent, a helpful AI assistant that can interact"
|
|
69
|
-
" with a computer to solve tasks."
|
|
70
|
-
)
|
|
70
|
+
_DEFAULT_SOUL = OPENHANDS_SYSTEM_IDENTITY
|
|
71
71
|
|
|
72
72
|
# Built-in prompt dir. The registry only stands in for built-in prompts here; a
|
|
73
73
|
# subclass with its own prompts/ keeps the Jinja render path.
|
|
@@ -162,12 +162,14 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
162
162
|
default_factory=lambda: [tool.__name__ for tool in BUILT_IN_TOOLS],
|
|
163
163
|
description=(
|
|
164
164
|
"List of default tool class names to include. By default, the agent "
|
|
165
|
-
"includes 'FinishTool'
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
165
|
+
"includes 'FinishTool'. Set to an empty list to disable all default "
|
|
166
|
+
"tools, or provide a subset to include specific ones. Add 'ThinkTool' "
|
|
167
|
+
"explicitly to enable the think tool. "
|
|
168
|
+
"Example: include_default_tools=['FinishTool', 'ThinkTool'] to enable "
|
|
169
|
+
"the think tool, or include_default_tools=[] to disable all default "
|
|
170
|
+
"tools."
|
|
169
171
|
),
|
|
170
|
-
examples=[["FinishTool", "
|
|
172
|
+
examples=[["FinishTool"], ["FinishTool", "ThinkTool"], []],
|
|
171
173
|
)
|
|
172
174
|
agent_context: AgentContext | None = Field(
|
|
173
175
|
default=None,
|
|
@@ -82,9 +82,11 @@ class StreamContext:
|
|
|
82
82
|
on_token: Callable[[Any], None] | None,
|
|
83
83
|
on_stream: StreamProgressCallbackType | None,
|
|
84
84
|
mask: Callable[[], StreamOutputMask],
|
|
85
|
+
last_seq: Callable[[], int | None] | None = None,
|
|
85
86
|
) -> None:
|
|
86
87
|
self.item_id = item_id
|
|
87
88
|
self._anchor_seq = anchor_seq
|
|
89
|
+
self._last_seq = last_seq
|
|
88
90
|
self._on_token = on_token
|
|
89
91
|
self._on_stream = on_stream
|
|
90
92
|
self._mask = mask
|
|
@@ -99,6 +101,7 @@ class StreamContext:
|
|
|
99
101
|
self._reserved = False
|
|
100
102
|
self._claimed = False
|
|
101
103
|
self._chunk_id: str | None = None
|
|
104
|
+
self._anchor_pending = False
|
|
102
105
|
|
|
103
106
|
@classmethod
|
|
104
107
|
def open(
|
|
@@ -108,13 +111,18 @@ class StreamContext:
|
|
|
108
111
|
) -> StreamContext:
|
|
109
112
|
"""Mint an id and read the anchor. Neither touches the event log."""
|
|
110
113
|
state = conversation.state
|
|
111
|
-
|
|
114
|
+
|
|
115
|
+
def last_seq() -> int | None:
|
|
116
|
+
length = len(state.events)
|
|
117
|
+
return length - 1 if length else None
|
|
118
|
+
|
|
112
119
|
return cls(
|
|
113
120
|
item_id=str(uuid.uuid4()),
|
|
114
|
-
anchor_seq=
|
|
121
|
+
anchor_seq=last_seq(),
|
|
115
122
|
on_token=on_token,
|
|
116
123
|
on_stream=conversation.on_stream,
|
|
117
124
|
mask=state.secret_registry.compile_stream_mask,
|
|
125
|
+
last_seq=last_seq,
|
|
118
126
|
)
|
|
119
127
|
|
|
120
128
|
@property
|
|
@@ -176,6 +184,28 @@ class StreamContext:
|
|
|
176
184
|
if self._reserved:
|
|
177
185
|
self._claimed = True
|
|
178
186
|
|
|
187
|
+
def next_item(self) -> None:
|
|
188
|
+
"""Open a fresh slot for the rest of the step, after a committed item.
|
|
189
|
+
|
|
190
|
+
For a step that emits several durable items in turn (an ACP turn's text
|
|
191
|
+
segments between its tool calls). The new slot anchors on the event log
|
|
192
|
+
as it stands when its first token arrives, so it streams below every
|
|
193
|
+
event emitted in between.
|
|
194
|
+
"""
|
|
195
|
+
self.close("no_durable_event")
|
|
196
|
+
self.item_id = str(uuid.uuid4())
|
|
197
|
+
self._anchor_seq = None
|
|
198
|
+
self._masks.clear()
|
|
199
|
+
self._attempt = 1
|
|
200
|
+
self._order = itertools.count()
|
|
201
|
+
self._started = False
|
|
202
|
+
self._opened = False
|
|
203
|
+
self._started_attempt = None
|
|
204
|
+
self._reserved = False
|
|
205
|
+
self._claimed = False
|
|
206
|
+
self._chunk_id = None
|
|
207
|
+
self._anchor_pending = self._last_seq is not None
|
|
208
|
+
|
|
179
209
|
def close(self, reason: str) -> None:
|
|
180
210
|
"""Emit ``StreamAborted`` unless a durable event committed the id.
|
|
181
211
|
|
|
@@ -211,6 +241,9 @@ class StreamContext:
|
|
|
211
241
|
self.new_attempt()
|
|
212
242
|
self._chunk_id = chunk_id
|
|
213
243
|
if not self._started:
|
|
244
|
+
if self._anchor_pending and self._last_seq is not None:
|
|
245
|
+
self._anchor_seq = self._last_seq()
|
|
246
|
+
self._anchor_pending = False
|
|
214
247
|
self._started = self._opened = True
|
|
215
248
|
self._started_attempt = self._attempt
|
|
216
249
|
self._emit(StreamStarted(self.item_id, self._attempt, self._anchor_seq))
|
|
@@ -26,7 +26,11 @@ from openhands.sdk.context.condenser.base import CondenserBase
|
|
|
26
26
|
from openhands.sdk.context.view import View
|
|
27
27
|
from openhands.sdk.event.base import LLMConvertibleEvent
|
|
28
28
|
from openhands.sdk.event.condenser import Condensation
|
|
29
|
-
from openhands.sdk.llm import LLM, Message
|
|
29
|
+
from openhands.sdk.llm import LLM, Message, TextContent
|
|
30
|
+
from openhands.sdk.llm.mixins.fn_call_converter import (
|
|
31
|
+
TERMINAL_COMMAND_PREFIX_ALIASES,
|
|
32
|
+
TOOL_NAME_ALIASES,
|
|
33
|
+
)
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
# Regex matching raw ASCII control characters (U+0000–U+001F) that are
|
|
@@ -240,17 +244,6 @@ def fix_malformed_tool_arguments(
|
|
|
240
244
|
return fixed_arguments
|
|
241
245
|
|
|
242
246
|
|
|
243
|
-
TOOL_NAME_ALIASES: dict[str, str] = {
|
|
244
|
-
"bash": "terminal",
|
|
245
|
-
"command": "terminal",
|
|
246
|
-
"execute": "terminal",
|
|
247
|
-
"execute_bash": "terminal",
|
|
248
|
-
"git": "terminal",
|
|
249
|
-
"reset": "terminal",
|
|
250
|
-
"str_replace": "file_editor",
|
|
251
|
-
"str_replace_editor": "file_editor",
|
|
252
|
-
}
|
|
253
|
-
|
|
254
247
|
# Regex to detect malformed tool names (e.g., "str_replace </parameter"
|
|
255
248
|
# or "str_replace</function>"). These occur when LLMs emit XML/HTML
|
|
256
249
|
# tag fragments in tool names. The leading identifier is extracted and
|
|
@@ -270,12 +263,6 @@ def _extract_tool_name_base(tool_name: str) -> str:
|
|
|
270
263
|
return match.group(1) if match else tool_name
|
|
271
264
|
|
|
272
265
|
|
|
273
|
-
# Terminal aliases that prepend the tool name to the command argument.
|
|
274
|
-
# Unlike 'bash' which passes through the command directly, these tools
|
|
275
|
-
# (e.g., 'git', 'reset') are themselves commands that should be combined
|
|
276
|
-
# with their arguments (e.g., 'git status', 'reset clear').
|
|
277
|
-
_TERMINAL_COMMAND_PREFIX_ALIASES = frozenset({"git", "reset"})
|
|
278
|
-
|
|
279
266
|
# This fallback is intentionally tiny: it only accepts exact, bare command names
|
|
280
267
|
# that are useful as read-only defaults when some models emit them as tool names.
|
|
281
268
|
_SHELL_TOOL_FALLBACK_COMMANDS = frozenset({"find", "git", "ls", "pwd"})
|
|
@@ -506,7 +493,7 @@ def normalize_tool_call(
|
|
|
506
493
|
# For terminal alias with prefix, combine tool name with command
|
|
507
494
|
if (
|
|
508
495
|
alias_target == "terminal"
|
|
509
|
-
and base_name in
|
|
496
|
+
and base_name in TERMINAL_COMMAND_PREFIX_ALIASES
|
|
510
497
|
):
|
|
511
498
|
original_command = arguments.get("command")
|
|
512
499
|
normalized_arguments = {
|
|
@@ -560,6 +547,34 @@ def normalize_tool_call(
|
|
|
560
547
|
return normalized_tool_name, normalized_arguments
|
|
561
548
|
|
|
562
549
|
|
|
550
|
+
_TRAILING_ASSISTANT_NUDGE = (
|
|
551
|
+
"Your previous response has already been delivered to the user. If the user "
|
|
552
|
+
"sent a new message above that your previous response did not address, "
|
|
553
|
+
"respond to it now. Otherwise, continue the task."
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _ensure_trailing_user_message(messages: list[Message]) -> None:
|
|
558
|
+
"""Append a request-only user turn when the history ends with the assistant.
|
|
559
|
+
|
|
560
|
+
A user message that lands while the LLM call is in flight is recorded before
|
|
561
|
+
the agent's reply (``astep`` releases the state lock for the network wait),
|
|
562
|
+
so the continued run would otherwise send ``[..., user, assistant]``. Chat
|
|
563
|
+
APIs expect the final turn to be a user turn, and Anthropic rejects a
|
|
564
|
+
trailing assistant message on models without prefill support. The nudge is
|
|
565
|
+
not persisted: it only shapes this request.
|
|
566
|
+
"""
|
|
567
|
+
if not messages or messages[-1].role != "assistant":
|
|
568
|
+
return
|
|
569
|
+
logger.warning(
|
|
570
|
+
"LLM history ends with an assistant message; appending a user turn so "
|
|
571
|
+
"the request does not end with assistant prefill."
|
|
572
|
+
)
|
|
573
|
+
messages.append(
|
|
574
|
+
Message(role="user", content=[TextContent(text=_TRAILING_ASSISTANT_NUDGE)])
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
563
578
|
@overload
|
|
564
579
|
def prepare_llm_messages(
|
|
565
580
|
view: View,
|
|
@@ -630,6 +645,7 @@ def prepare_llm_messages(
|
|
|
630
645
|
if additional_messages:
|
|
631
646
|
messages.extend(additional_messages)
|
|
632
647
|
|
|
648
|
+
_ensure_trailing_user_message(messages)
|
|
633
649
|
return messages
|
|
634
650
|
|
|
635
651
|
|
|
@@ -665,4 +681,5 @@ async def aprepare_llm_messages(
|
|
|
665
681
|
if additional_messages:
|
|
666
682
|
messages.extend(additional_messages)
|
|
667
683
|
|
|
684
|
+
_ensure_trailing_user_message(messages)
|
|
668
685
|
return messages
|
{openhands_sdk-1.59.0 → openhands_sdk-1.60.0}/openhands/sdk/context/prompts/sections/static.py
RENAMED
|
@@ -19,6 +19,9 @@ from openhands.sdk.context.prompts.section import (
|
|
|
19
19
|
Platform,
|
|
20
20
|
PromptContext,
|
|
21
21
|
)
|
|
22
|
+
from openhands.sdk.llm.utils.openhands_provider import (
|
|
23
|
+
OPENHANDS_SYSTEM_IDENTITY,
|
|
24
|
+
)
|
|
22
25
|
from openhands.sdk.utils.path import get_user_persistence_dir, to_posix_path
|
|
23
26
|
|
|
24
27
|
|
|
@@ -81,10 +84,7 @@ class _StaticTextSection:
|
|
|
81
84
|
class SoulSection(_StaticTextSection):
|
|
82
85
|
name = "soul"
|
|
83
86
|
|
|
84
|
-
_DEFAULT_SOUL =
|
|
85
|
-
"You are OpenHands agent, a helpful AI assistant that can interact"
|
|
86
|
-
" with a computer to solve tasks."
|
|
87
|
-
)
|
|
87
|
+
_DEFAULT_SOUL = OPENHANDS_SYSTEM_IDENTITY
|
|
88
88
|
|
|
89
89
|
def render(self, ctx: PromptContext) -> str | None:
|
|
90
90
|
soul = str(ctx.template_kwargs.get("soul_content") or self._DEFAULT_SOUL)
|
|
@@ -258,6 +258,13 @@ class BaseConversation(ABC):
|
|
|
258
258
|
|
|
259
259
|
This method runs the agent until it finishes processing the current
|
|
260
260
|
message or reaches the maximum iteration limit.
|
|
261
|
+
|
|
262
|
+
When the run ends because the iteration limit is reached
|
|
263
|
+
(``ERROR``) or the agent is stuck (``STUCK``), the implementations
|
|
264
|
+
differ: ``LocalConversation.run()`` returns and leaves that status in
|
|
265
|
+
``state.execution_status``, while ``RemoteConversation.run()`` raises
|
|
266
|
+
:class:`ConversationRunError`. Errors raised during the run, such as
|
|
267
|
+
LLM errors, raise ``ConversationRunError`` in both.
|
|
261
268
|
"""
|
|
262
269
|
...
|
|
263
270
|
|
|
@@ -472,10 +479,10 @@ class BaseConversation(ABC):
|
|
|
472
479
|
if ``None``).
|
|
473
480
|
agent: Agent for the fork. Defaults to a deep-copy of the
|
|
474
481
|
source agent.
|
|
475
|
-
title:
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
482
|
+
title: Honoured by ``RemoteConversation`` (passed to the agent
|
|
483
|
+
server which stores it on ``StoredConversation``); accepted
|
|
484
|
+
but ignored by ``LocalConversation``, which has no title
|
|
485
|
+
field.
|
|
479
486
|
tags: Optional tags for the forked conversation.
|
|
480
487
|
reset_metrics: If ``True`` (default), cost/token stats start
|
|
481
488
|
fresh on the fork.
|
|
@@ -339,11 +339,7 @@ class EventLog(EventsListBase):
|
|
|
339
339
|
except Exception as e:
|
|
340
340
|
logger.warning("Error listing event directory %s: %s", self._dir, e)
|
|
341
341
|
return 0
|
|
342
|
-
return sum(
|
|
343
|
-
1
|
|
344
|
-
for p in paths
|
|
345
|
-
if posix_path_name(p).startswith("event-") and p.endswith(".json")
|
|
346
|
-
)
|
|
342
|
+
return sum(1 for p in paths if EVENT_NAME_RE.match(posix_path_name(p)))
|
|
347
343
|
|
|
348
344
|
def _sync_from_disk(self, disk_length: int) -> None:
|
|
349
345
|
"""Sync state for events written by other processes.
|