openhands-sdk 1.46.0__tar.gz → 1.48.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.46.0 → openhands_sdk-1.48.0}/PKG-INFO +1 -1
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_agent.py +90 -4
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/agent.py +58 -18
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/response_dispatch.py +29 -4
- openhands_sdk-1.48.0/openhands/sdk/agent/stream_context.py +294 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/utils.py +1 -88
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +2 -11
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/impl/local_conversation.py +28 -6
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/impl/remote_conversation.py +177 -71
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/secret_registry.py +66 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/title_utils.py +24 -7
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/executor.py +1 -2
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/cleanup_profile.py +4 -10
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/__init__.py +2 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/classifier.py +22 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm.py +78 -29
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/retry_mixin.py +16 -2
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/telemetry.py +14 -20
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/agent_profile.py +24 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/resolver.py +7 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/model.py +23 -1
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/vision_inspect.py +1 -3
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/models.py +5 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/redact.py +1 -1
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +46 -3
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/base.py +25 -2
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +110 -46
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/SOURCES.txt +2 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/pyproject.toml +2 -2
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/sections/static.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/request.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/acp_install_catalog.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/requires.txt +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.46.0 → openhands_sdk-1.48.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.48.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
|
|
@@ -82,6 +82,7 @@ from openhands.sdk.agent.acp_file_credentials import (
|
|
|
82
82
|
from openhands.sdk.agent.acp_models import ACPModelInfo
|
|
83
83
|
from openhands.sdk.agent.acp_tracing import ACPTurnTrace
|
|
84
84
|
from openhands.sdk.agent.base import AgentBase
|
|
85
|
+
from openhands.sdk.agent.stream_context import StreamContext
|
|
85
86
|
from openhands.sdk.context import AgentContext
|
|
86
87
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
87
88
|
from openhands.sdk.credential import (
|
|
@@ -1926,6 +1927,10 @@ class ACPAgent(AgentBase):
|
|
|
1926
1927
|
_suffix_install_state: str = PrivateAttr(default="unused")
|
|
1927
1928
|
_installed_suffix: str | None = PrivateAttr(default=None)
|
|
1928
1929
|
_restart_session_on_next_turn: bool = PrivateAttr(default=False)
|
|
1930
|
+
# Stream identity for the turn in flight; see stream_context.py. Held on
|
|
1931
|
+
# the agent rather than threaded through the finalizers because the ACP
|
|
1932
|
+
# turn already resolves through four of them.
|
|
1933
|
+
_stream: StreamContext | None = PrivateAttr(default=None)
|
|
1929
1934
|
_resumed_existing_session: bool = PrivateAttr(default=False)
|
|
1930
1935
|
_file_credential_lifecycles: dict[str, ACPFileCredentialLifecycle] = PrivateAttr(
|
|
1931
1936
|
default_factory=dict
|
|
@@ -1957,7 +1962,7 @@ class ACPAgent(AgentBase):
|
|
|
1957
1962
|
self._file_credential_bindings[secret_name] = binding
|
|
1958
1963
|
|
|
1959
1964
|
def restart_for_updated_credentials(self, secret_names: Collection[str]) -> None:
|
|
1960
|
-
configured = {spec.secret_name for spec in self.
|
|
1965
|
+
configured = {spec.secret_name for spec in self._active_file_secrets()}
|
|
1961
1966
|
with self._file_credential_lock:
|
|
1962
1967
|
self._replace_file_credentials_on_next_materialisation.update(
|
|
1963
1968
|
configured.intersection(secret_names)
|
|
@@ -2452,7 +2457,7 @@ class ACPAgent(AgentBase):
|
|
|
2452
2457
|
(their values are file blobs, not env vars the subprocess can reference
|
|
2453
2458
|
by name).
|
|
2454
2459
|
"""
|
|
2455
|
-
configured = {spec.secret_name for spec in self.
|
|
2460
|
+
configured = {spec.secret_name for spec in self._active_file_secrets()}
|
|
2456
2461
|
if not configured:
|
|
2457
2462
|
return set()
|
|
2458
2463
|
return set(state.secret_registry.secret_sources) & configured
|
|
@@ -2587,6 +2592,42 @@ class ACPAgent(AgentBase):
|
|
|
2587
2592
|
self.acp_server or ""
|
|
2588
2593
|
) or detect_acp_provider_by_command(self.acp_command)
|
|
2589
2594
|
|
|
2595
|
+
def _active_file_secrets(self) -> list[ACPFileSecretSpec]:
|
|
2596
|
+
"""The file-secret specs that apply to the provider this agent runs.
|
|
2597
|
+
|
|
2598
|
+
Drops the specs that are *another registered provider's* reserved
|
|
2599
|
+
credential and keeps everything else, so a harness added upstream cannot
|
|
2600
|
+
change how this provider's conversation treats a secret carrying the new
|
|
2601
|
+
reserved name (see #4923). A name several providers share stays: it is
|
|
2602
|
+
this provider's too.
|
|
2603
|
+
|
|
2604
|
+
Deliberately no provenance test. :attr:`acp_file_secrets` defaults to the
|
|
2605
|
+
union across the registry, but a persisted conversation carries whatever
|
|
2606
|
+
that union was when it was written, so comparing against today's default
|
|
2607
|
+
would read an older list as a caller override and silently stop scoping
|
|
2608
|
+
after an upgrade. Filtering by ownership needs no such distinction, and a
|
|
2609
|
+
spec for a CLI outside the registry is owned by nobody and always applies.
|
|
2610
|
+
|
|
2611
|
+
An unrecognised server keeps every spec, matching
|
|
2612
|
+
:meth:`_strip_conflicting_env`: without an identity we cannot tell whose
|
|
2613
|
+
credential a reserved name belongs to.
|
|
2614
|
+
"""
|
|
2615
|
+
provider = self._resolved_provider()
|
|
2616
|
+
if provider is None:
|
|
2617
|
+
return list(self.acp_file_secrets)
|
|
2618
|
+
own = {spec.secret_name for spec in provider.file_secrets}
|
|
2619
|
+
owned_elsewhere = {
|
|
2620
|
+
spec.secret_name
|
|
2621
|
+
for key, info in ACP_PROVIDERS.items()
|
|
2622
|
+
if key != provider.key
|
|
2623
|
+
for spec in info.file_secrets
|
|
2624
|
+
} - own
|
|
2625
|
+
return [
|
|
2626
|
+
spec
|
|
2627
|
+
for spec in self.acp_file_secrets
|
|
2628
|
+
if spec.secret_name not in owned_elsewhere
|
|
2629
|
+
]
|
|
2630
|
+
|
|
2590
2631
|
def _strip_conflicting_env(self, env: dict[str, str]) -> None:
|
|
2591
2632
|
"""Remove env vars that would defeat this provider's own credential.
|
|
2592
2633
|
|
|
@@ -2615,7 +2656,7 @@ class ACPAgent(AgentBase):
|
|
|
2615
2656
|
def _materialise_file_secrets(
|
|
2616
2657
|
self, state: ConversationState, env: dict[str, str]
|
|
2617
2658
|
) -> None:
|
|
2618
|
-
for spec in self.
|
|
2659
|
+
for spec in self._active_file_secrets():
|
|
2619
2660
|
name = spec.secret_name
|
|
2620
2661
|
with self._file_credential_lock:
|
|
2621
2662
|
replace_existing = (
|
|
@@ -3091,7 +3132,7 @@ class ACPAgent(AgentBase):
|
|
|
3091
3132
|
or detect_acp_provider_by_agent_name(agent_name)
|
|
3092
3133
|
)
|
|
3093
3134
|
configured = _preconfigured_credentials(
|
|
3094
|
-
auth_provider, self.
|
|
3135
|
+
auth_provider, self._active_file_secrets(), env
|
|
3095
3136
|
)
|
|
3096
3137
|
if configured:
|
|
3097
3138
|
logger.info(
|
|
@@ -3688,7 +3729,13 @@ class ACPAgent(AgentBase):
|
|
|
3688
3729
|
# completed turn for eval/remote consumers, matching #2190.
|
|
3689
3730
|
finish_action = FinishAction(message=response_text)
|
|
3690
3731
|
tc_id = str(uuid.uuid4())
|
|
3732
|
+
# An ACP turn's streamed text lands here, not in a MessageEvent, so
|
|
3733
|
+
# this is the event that retires the stream's slot.
|
|
3734
|
+
minted: dict[str, Any] = {}
|
|
3735
|
+
if self._stream is not None and (item_id := self._stream.claim()):
|
|
3736
|
+
minted["id"] = item_id
|
|
3691
3737
|
action_event = ActionEvent(
|
|
3738
|
+
**minted,
|
|
3692
3739
|
source="agent",
|
|
3693
3740
|
thought=[],
|
|
3694
3741
|
reasoning_content=thought_text or None,
|
|
@@ -3704,6 +3751,8 @@ class ACPAgent(AgentBase):
|
|
|
3704
3751
|
llm_response_id=str(uuid.uuid4()),
|
|
3705
3752
|
)
|
|
3706
3753
|
on_event(action_event)
|
|
3754
|
+
if self._stream is not None and minted:
|
|
3755
|
+
self._stream.commit()
|
|
3707
3756
|
on_event(
|
|
3708
3757
|
ObservationEvent(
|
|
3709
3758
|
observation=FinishObservation.from_text(text=response_text),
|
|
@@ -3865,6 +3914,19 @@ class ACPAgent(AgentBase):
|
|
|
3865
3914
|
(``LocalConversation.arun``) goes through :meth:`astep`, which
|
|
3866
3915
|
avoids the cross-thread state-lock deadlock described in #3348.
|
|
3867
3916
|
"""
|
|
3917
|
+
with StreamContext.open(conversation, on_token) as stream:
|
|
3918
|
+
self._stream = stream
|
|
3919
|
+
try:
|
|
3920
|
+
self._step(conversation, on_event, stream.token_callback)
|
|
3921
|
+
finally:
|
|
3922
|
+
self._stream = None
|
|
3923
|
+
|
|
3924
|
+
def _step(
|
|
3925
|
+
self,
|
|
3926
|
+
conversation: LocalConversation,
|
|
3927
|
+
on_event: ConversationCallbackType,
|
|
3928
|
+
on_token: ConversationTokenCallbackType | None = None,
|
|
3929
|
+
) -> None:
|
|
3868
3930
|
state = conversation.state
|
|
3869
3931
|
|
|
3870
3932
|
if self._restart_session_on_next_turn:
|
|
@@ -3933,6 +3995,8 @@ class ACPAgent(AgentBase):
|
|
|
3933
3995
|
)
|
|
3934
3996
|
time.sleep(delay)
|
|
3935
3997
|
self._cancel_inflight_tool_calls()
|
|
3998
|
+
if self._stream is not None:
|
|
3999
|
+
self._stream.new_attempt()
|
|
3936
4000
|
self._reset_client_for_turn(
|
|
3937
4001
|
on_token,
|
|
3938
4002
|
on_event,
|
|
@@ -3963,6 +4027,8 @@ class ACPAgent(AgentBase):
|
|
|
3963
4027
|
)
|
|
3964
4028
|
time.sleep(delay)
|
|
3965
4029
|
self._cancel_inflight_tool_calls()
|
|
4030
|
+
if self._stream is not None:
|
|
4031
|
+
self._stream.new_attempt()
|
|
3966
4032
|
self._reset_client_for_turn(
|
|
3967
4033
|
on_token,
|
|
3968
4034
|
on_event,
|
|
@@ -4023,6 +4089,22 @@ class ACPAgent(AgentBase):
|
|
|
4023
4089
|
supplied by ``LocalConversation.arun`` is responsible for taking
|
|
4024
4090
|
the state lock around each individual event.
|
|
4025
4091
|
"""
|
|
4092
|
+
with StreamContext.open(conversation, on_token) as stream:
|
|
4093
|
+
self._stream = stream
|
|
4094
|
+
try:
|
|
4095
|
+
await self._astep(
|
|
4096
|
+
conversation, on_event, stream.token_callback, prompt_message
|
|
4097
|
+
)
|
|
4098
|
+
finally:
|
|
4099
|
+
self._stream = None
|
|
4100
|
+
|
|
4101
|
+
async def _astep(
|
|
4102
|
+
self,
|
|
4103
|
+
conversation: LocalConversation,
|
|
4104
|
+
on_event: ConversationCallbackType,
|
|
4105
|
+
on_token: ConversationTokenCallbackType | None = None,
|
|
4106
|
+
prompt_message: MessageEvent | None = None,
|
|
4107
|
+
) -> None:
|
|
4026
4108
|
state = conversation.state
|
|
4027
4109
|
|
|
4028
4110
|
if self._restart_session_on_next_turn:
|
|
@@ -4099,6 +4181,8 @@ class ACPAgent(AgentBase):
|
|
|
4099
4181
|
)
|
|
4100
4182
|
await asyncio.sleep(delay)
|
|
4101
4183
|
self._cancel_inflight_tool_calls()
|
|
4184
|
+
if self._stream is not None:
|
|
4185
|
+
self._stream.new_attempt()
|
|
4102
4186
|
self._reset_client_for_turn(
|
|
4103
4187
|
on_token,
|
|
4104
4188
|
on_event,
|
|
@@ -4126,6 +4210,8 @@ class ACPAgent(AgentBase):
|
|
|
4126
4210
|
)
|
|
4127
4211
|
await asyncio.sleep(delay)
|
|
4128
4212
|
self._cancel_inflight_tool_calls()
|
|
4213
|
+
if self._stream is not None:
|
|
4214
|
+
self._stream.new_attempt()
|
|
4129
4215
|
self._reset_client_for_turn(
|
|
4130
4216
|
on_token,
|
|
4131
4217
|
on_event,
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import re
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from pydantic import PrivateAttr, ValidationError, model_validator
|
|
10
10
|
|
|
@@ -18,11 +18,10 @@ from openhands.sdk.agent.response_dispatch import (
|
|
|
18
18
|
ResponseDispatchMixin,
|
|
19
19
|
classify_response,
|
|
20
20
|
)
|
|
21
|
+
from openhands.sdk.agent.stream_context import StreamContext
|
|
21
22
|
from openhands.sdk.agent.utils import (
|
|
22
|
-
amake_llm_completion,
|
|
23
23
|
aprepare_llm_messages,
|
|
24
24
|
fix_malformed_tool_arguments,
|
|
25
|
-
make_llm_completion,
|
|
26
25
|
normalize_tool_call,
|
|
27
26
|
parse_tool_call_arguments,
|
|
28
27
|
prepare_llm_messages,
|
|
@@ -535,7 +534,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
535
534
|
system_prompt=TextContent(text=self.static_system_message),
|
|
536
535
|
# Tools are stored as ToolDefinition objects and converted to
|
|
537
536
|
# OpenAI format with security_risk parameter during LLM completion.
|
|
538
|
-
#
|
|
537
|
+
# Agent calls always expose security risk prediction in tool schemas.
|
|
539
538
|
tools=list(self.tools_map.values()),
|
|
540
539
|
dynamic_context=TextContent(text=dynamic_context)
|
|
541
540
|
if dynamic_context
|
|
@@ -639,6 +638,15 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
639
638
|
conversation: LocalConversation,
|
|
640
639
|
on_event: ConversationCallbackType,
|
|
641
640
|
on_token: ConversationTokenCallbackType | None = None,
|
|
641
|
+
) -> None:
|
|
642
|
+
with StreamContext.open(conversation, on_token) as stream:
|
|
643
|
+
self._step(conversation, on_event, stream)
|
|
644
|
+
|
|
645
|
+
def _step(
|
|
646
|
+
self,
|
|
647
|
+
conversation: LocalConversation,
|
|
648
|
+
on_event: ConversationCallbackType,
|
|
649
|
+
stream: StreamContext,
|
|
642
650
|
) -> None:
|
|
643
651
|
state = conversation.state
|
|
644
652
|
# Check for pending actions (implicit confirmation)
|
|
@@ -717,11 +725,12 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
717
725
|
)
|
|
718
726
|
|
|
719
727
|
try:
|
|
720
|
-
llm_response =
|
|
721
|
-
|
|
722
|
-
_messages,
|
|
728
|
+
llm_response = self.llm.generate(
|
|
729
|
+
messages=_messages,
|
|
723
730
|
tools=list(self.tools_map.values()),
|
|
724
|
-
|
|
731
|
+
store=False,
|
|
732
|
+
add_security_risk_prediction=True,
|
|
733
|
+
on_token=stream.token_callback,
|
|
725
734
|
call_context=call_context,
|
|
726
735
|
)
|
|
727
736
|
except FunctionCallValidationError as e:
|
|
@@ -807,11 +816,11 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
807
816
|
match response_type:
|
|
808
817
|
case LLMResponseType.TOOL_CALLS:
|
|
809
818
|
self._handle_tool_calls(
|
|
810
|
-
message, llm_response, conversation, state, on_event
|
|
819
|
+
message, llm_response, conversation, state, on_event, stream
|
|
811
820
|
)
|
|
812
821
|
case LLMResponseType.CONTENT:
|
|
813
822
|
self._handle_content_response(
|
|
814
|
-
message, llm_response, conversation, state, on_event
|
|
823
|
+
message, llm_response, conversation, state, on_event, stream
|
|
815
824
|
)
|
|
816
825
|
case LLMResponseType.REASONING_ONLY | LLMResponseType.EMPTY:
|
|
817
826
|
self._handle_no_content_response(
|
|
@@ -820,6 +829,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
820
829
|
conversation,
|
|
821
830
|
state,
|
|
822
831
|
on_event,
|
|
832
|
+
stream,
|
|
823
833
|
response_type=response_type,
|
|
824
834
|
)
|
|
825
835
|
|
|
@@ -833,12 +843,21 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
833
843
|
"""Async variant of :meth:`step`.
|
|
834
844
|
|
|
835
845
|
The LLM completion is performed asynchronously via
|
|
836
|
-
:
|
|
846
|
+
:meth:`LLM.agenerate`. Tool dispatch uses
|
|
837
847
|
:meth:`_aexecute_actions` which runs each tool call in its own
|
|
838
848
|
thread via :func:`asyncio.loop.run_in_executor` and schedules
|
|
839
849
|
parallel calls with :func:`asyncio.gather`, keeping the event
|
|
840
850
|
loop responsive during blocking tool I/O.
|
|
841
851
|
"""
|
|
852
|
+
with StreamContext.open(conversation, on_token) as stream:
|
|
853
|
+
await self._astep(conversation, on_event, stream)
|
|
854
|
+
|
|
855
|
+
async def _astep(
|
|
856
|
+
self,
|
|
857
|
+
conversation: LocalConversation,
|
|
858
|
+
on_event: ConversationCallbackType,
|
|
859
|
+
stream: StreamContext,
|
|
860
|
+
) -> None:
|
|
842
861
|
state = conversation.state
|
|
843
862
|
# Check for pending actions (implicit confirmation)
|
|
844
863
|
pending_actions = ConversationState.get_unmatched_actions(state.active_branch())
|
|
@@ -914,11 +933,12 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
914
933
|
# and state snapshots aren't blocked for the whole response. No-op
|
|
915
934
|
# unless the run loop holds the lock (e.g. direct astep() in tests).
|
|
916
935
|
async with conversation._released_state_lock_during_io():
|
|
917
|
-
llm_response = await
|
|
918
|
-
|
|
919
|
-
_messages,
|
|
936
|
+
llm_response = await self.llm.agenerate(
|
|
937
|
+
messages=_messages,
|
|
920
938
|
tools=list(self.tools_map.values()),
|
|
921
|
-
|
|
939
|
+
store=False,
|
|
940
|
+
add_security_risk_prediction=True,
|
|
941
|
+
on_token=stream.token_callback,
|
|
922
942
|
call_context=call_context,
|
|
923
943
|
)
|
|
924
944
|
except FunctionCallValidationError as e:
|
|
@@ -1006,11 +1026,11 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1006
1026
|
match response_type:
|
|
1007
1027
|
case LLMResponseType.TOOL_CALLS:
|
|
1008
1028
|
await self._ahandle_tool_calls(
|
|
1009
|
-
message, llm_response, conversation, state, on_event
|
|
1029
|
+
message, llm_response, conversation, state, on_event, stream
|
|
1010
1030
|
)
|
|
1011
1031
|
case LLMResponseType.CONTENT:
|
|
1012
1032
|
self._handle_content_response(
|
|
1013
|
-
message, llm_response, conversation, state, on_event
|
|
1033
|
+
message, llm_response, conversation, state, on_event, stream
|
|
1014
1034
|
)
|
|
1015
1035
|
case LLMResponseType.REASONING_ONLY | LLMResponseType.EMPTY:
|
|
1016
1036
|
self._handle_no_content_response(
|
|
@@ -1019,6 +1039,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1019
1039
|
conversation,
|
|
1020
1040
|
state,
|
|
1021
1041
|
on_event,
|
|
1042
|
+
stream,
|
|
1022
1043
|
response_type=response_type,
|
|
1023
1044
|
)
|
|
1024
1045
|
|
|
@@ -1153,6 +1174,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1153
1174
|
reasoning_content: str | None = None,
|
|
1154
1175
|
thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
|
|
1155
1176
|
responses_reasoning_item: ReasoningItemModel | None = None,
|
|
1177
|
+
stream: StreamContext | None = None,
|
|
1156
1178
|
) -> None:
|
|
1157
1179
|
try:
|
|
1158
1180
|
json.loads(tool_call.arguments)
|
|
@@ -1168,7 +1190,11 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1168
1190
|
}
|
|
1169
1191
|
)
|
|
1170
1192
|
|
|
1193
|
+
minted: dict[str, Any] = {}
|
|
1194
|
+
if stream is not None and (item_id := stream.claim()):
|
|
1195
|
+
minted["id"] = item_id
|
|
1171
1196
|
tc_event = ActionEvent(
|
|
1197
|
+
**minted,
|
|
1172
1198
|
source="agent",
|
|
1173
1199
|
thought=thought or [],
|
|
1174
1200
|
reasoning_content=reasoning_content,
|
|
@@ -1181,6 +1207,8 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1181
1207
|
action=None,
|
|
1182
1208
|
)
|
|
1183
1209
|
on_event(tc_event)
|
|
1210
|
+
if stream is not None and minted:
|
|
1211
|
+
stream.commit()
|
|
1184
1212
|
error_event = AgentErrorEvent(
|
|
1185
1213
|
error=error,
|
|
1186
1214
|
tool_name=tool_name,
|
|
@@ -1207,6 +1235,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1207
1235
|
reasoning_content: str | None = None,
|
|
1208
1236
|
thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
|
|
1209
1237
|
responses_reasoning_item: ReasoningItemModel | None = None,
|
|
1238
|
+
stream: StreamContext | None = None,
|
|
1210
1239
|
) -> ActionEvent | None:
|
|
1211
1240
|
"""Converts a tool call into an ActionEvent, validating arguments.
|
|
1212
1241
|
|
|
@@ -1249,6 +1278,7 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1249
1278
|
reasoning_content=reasoning_content,
|
|
1250
1279
|
thinking_blocks=thinking_blocks,
|
|
1251
1280
|
responses_reasoning_item=responses_reasoning_item,
|
|
1281
|
+
stream=stream,
|
|
1252
1282
|
)
|
|
1253
1283
|
return
|
|
1254
1284
|
|
|
@@ -1311,11 +1341,19 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1311
1341
|
reasoning_content=reasoning_content,
|
|
1312
1342
|
thinking_blocks=thinking_blocks,
|
|
1313
1343
|
responses_reasoning_item=responses_reasoning_item,
|
|
1344
|
+
stream=stream,
|
|
1314
1345
|
)
|
|
1315
1346
|
return
|
|
1316
1347
|
|
|
1317
|
-
# Create initial action event
|
|
1348
|
+
# Create initial action event. Claimed here rather than at the call
|
|
1349
|
+
# site so an error path, which never builds this event, leaves the slot
|
|
1350
|
+
# open for the abort in StreamContext.__exit__.
|
|
1351
|
+
minted: dict[str, Any] = {}
|
|
1352
|
+
if stream is not None and (item_id := stream.claim()):
|
|
1353
|
+
minted["id"] = item_id
|
|
1354
|
+
|
|
1318
1355
|
action_event = ActionEvent(
|
|
1356
|
+
**minted,
|
|
1319
1357
|
action=action,
|
|
1320
1358
|
thought=thought or [],
|
|
1321
1359
|
reasoning_content=reasoning_content,
|
|
@@ -1339,6 +1377,8 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
|
|
|
1339
1377
|
)
|
|
1340
1378
|
|
|
1341
1379
|
on_event(action_event)
|
|
1380
|
+
if stream is not None and minted:
|
|
1381
|
+
stream.commit()
|
|
1342
1382
|
return action_event
|
|
1343
1383
|
|
|
1344
1384
|
def _execute_action_event(
|
|
@@ -9,8 +9,9 @@ Contains:
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
from enum import StrEnum
|
|
12
|
-
from typing import TYPE_CHECKING
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
13
|
|
|
14
|
+
from openhands.sdk.agent.stream_context import StreamContext
|
|
14
15
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
15
16
|
from openhands.sdk.event import MessageEvent
|
|
16
17
|
from openhands.sdk.llm import LLMResponse, Message, TextContent
|
|
@@ -106,6 +107,7 @@ class ResponseDispatchMixin:
|
|
|
106
107
|
list[ThinkingBlock | RedactedThinkingBlock] | None
|
|
107
108
|
) = None,
|
|
108
109
|
responses_reasoning_item: ReasoningItemModel | None = None,
|
|
110
|
+
stream: StreamContext | None = None,
|
|
109
111
|
) -> ActionEvent | None: ...
|
|
110
112
|
|
|
111
113
|
def _execute_actions(
|
|
@@ -147,6 +149,7 @@ class ResponseDispatchMixin:
|
|
|
147
149
|
conversation: LocalConversation,
|
|
148
150
|
state: ConversationState,
|
|
149
151
|
on_event: ConversationCallbackType,
|
|
152
|
+
stream: StreamContext | None = None,
|
|
150
153
|
) -> None:
|
|
151
154
|
"""Handle LLM response containing tool calls."""
|
|
152
155
|
if not all(isinstance(c, TextContent) for c in message.content):
|
|
@@ -172,6 +175,9 @@ class ResponseDispatchMixin:
|
|
|
172
175
|
responses_reasoning_item=(
|
|
173
176
|
message.responses_reasoning_item if i == 0 else None
|
|
174
177
|
),
|
|
178
|
+
# The streamed text is this action's thought, so the first
|
|
179
|
+
# action event is what retires the slot.
|
|
180
|
+
stream=stream if i == 0 else None,
|
|
175
181
|
)
|
|
176
182
|
if action_event is None:
|
|
177
183
|
continue
|
|
@@ -192,6 +198,7 @@ class ResponseDispatchMixin:
|
|
|
192
198
|
conversation: LocalConversation,
|
|
193
199
|
state: ConversationState,
|
|
194
200
|
on_event: ConversationCallbackType,
|
|
201
|
+
stream: StreamContext | None = None,
|
|
195
202
|
) -> None:
|
|
196
203
|
"""Async variant of :meth:`_handle_tool_calls`.
|
|
197
204
|
|
|
@@ -222,6 +229,9 @@ class ResponseDispatchMixin:
|
|
|
222
229
|
responses_reasoning_item=(
|
|
223
230
|
message.responses_reasoning_item if i == 0 else None
|
|
224
231
|
),
|
|
232
|
+
# The streamed text is this action's thought, so the first
|
|
233
|
+
# action event is what retires the slot.
|
|
234
|
+
stream=stream if i == 0 else None,
|
|
225
235
|
)
|
|
226
236
|
if action_event is None:
|
|
227
237
|
continue
|
|
@@ -242,9 +252,10 @@ class ResponseDispatchMixin:
|
|
|
242
252
|
conversation: LocalConversation,
|
|
243
253
|
state: ConversationState,
|
|
244
254
|
on_event: ConversationCallbackType,
|
|
255
|
+
stream: StreamContext | None = None,
|
|
245
256
|
) -> None:
|
|
246
257
|
"""Handle LLM response with text content — finishes conversation."""
|
|
247
|
-
self._emit_message_event(message, llm_response, conversation, on_event)
|
|
258
|
+
self._emit_message_event(message, llm_response, conversation, on_event, stream)
|
|
248
259
|
self._maybe_emit_vllm_tokens(llm_response, on_event)
|
|
249
260
|
logger.debug("LLM produced a message response - awaits user input")
|
|
250
261
|
state.execution_status = ConversationExecutionStatus.FINISHED
|
|
@@ -256,6 +267,7 @@ class ResponseDispatchMixin:
|
|
|
256
267
|
conversation: LocalConversation,
|
|
257
268
|
state: ConversationState, # noqa: ARG002
|
|
258
269
|
on_event: ConversationCallbackType,
|
|
270
|
+
stream: StreamContext | None = None,
|
|
259
271
|
*,
|
|
260
272
|
response_type: LLMResponseType,
|
|
261
273
|
) -> None:
|
|
@@ -267,7 +279,7 @@ class ResponseDispatchMixin:
|
|
|
267
279
|
"""
|
|
268
280
|
if response_type is LLMResponseType.EMPTY:
|
|
269
281
|
logger.warning("LLM produced empty response - continuing agent loop")
|
|
270
|
-
self._emit_message_event(message, llm_response, conversation, on_event)
|
|
282
|
+
self._emit_message_event(message, llm_response, conversation, on_event, stream)
|
|
271
283
|
self._maybe_emit_vllm_tokens(llm_response, on_event)
|
|
272
284
|
self._send_corrective_nudge(on_event)
|
|
273
285
|
|
|
@@ -277,9 +289,18 @@ class ResponseDispatchMixin:
|
|
|
277
289
|
llm_response: LLMResponse,
|
|
278
290
|
conversation: LocalConversation,
|
|
279
291
|
on_event: ConversationCallbackType,
|
|
292
|
+
stream: StreamContext | None = None,
|
|
280
293
|
) -> MessageEvent:
|
|
281
|
-
"""Create and emit a MessageEvent, running critic if configured.
|
|
294
|
+
"""Create and emit a MessageEvent, running critic if configured.
|
|
295
|
+
|
|
296
|
+
The message carries the id its own stream minted, so a client holding
|
|
297
|
+
an open slot retires it on ``frame.event.id == slot.item_id``.
|
|
298
|
+
"""
|
|
299
|
+
minted: dict[str, Any] = {}
|
|
300
|
+
if stream is not None and (item_id := stream.claim()):
|
|
301
|
+
minted["id"] = item_id
|
|
282
302
|
msg_event = MessageEvent(
|
|
303
|
+
**minted,
|
|
283
304
|
source="agent",
|
|
284
305
|
llm_message=self._mask_secrets(message, conversation),
|
|
285
306
|
llm_response_id=llm_response.id,
|
|
@@ -291,6 +312,10 @@ class ResponseDispatchMixin:
|
|
|
291
312
|
update={"critic_result": critic_result}
|
|
292
313
|
)
|
|
293
314
|
on_event(msg_event)
|
|
315
|
+
# Retired only now: on_event can raise while persisting, and a slot
|
|
316
|
+
# retired before that leaves the client holding it open forever.
|
|
317
|
+
if stream is not None and minted:
|
|
318
|
+
stream.commit()
|
|
294
319
|
return msg_event
|
|
295
320
|
|
|
296
321
|
@staticmethod
|