openhands-sdk 1.51.0__tar.gz → 1.52.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.51.0 → openhands_sdk-1.52.0}/PKG-INFO +2 -2
- openhands_sdk-1.52.0/openhands/sdk/automation/__init__.py +215 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/base.py +16 -1
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/conversation.py +5 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/local_conversation.py +2 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/remote_conversation.py +36 -1
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/request.py +8 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/observability/laminar.py +118 -3
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/base.py +2 -1
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +8 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/base.py +8 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +17 -1
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/PKG-INFO +2 -2
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/SOURCES.txt +2 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/requires.txt +1 -1
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/pyproject.toml +2 -2
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_agent.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/agent.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/stream_context.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/sections/static.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/launch/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/launch/errors.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/launch/finalize.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/launch/preview.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/launch/resolve.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/call_context.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/meta_profile_store.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/registry.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/marketplace/types.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/oauth.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/fetch.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/profiles/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/profiles/agent_profile.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/profiles/profile_refs.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/profiles/resolver.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/settings/acp_install_catalog.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/settings/model.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/installed.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/subagent/scope.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/defaults.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/masking.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/redact.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.51.0 → openhands_sdk-1.52.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.52.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
|
|
@@ -18,7 +18,7 @@ Requires-Dist: joserfc>=1.7.5
|
|
|
18
18
|
Requires-Dist: litellm>=1.93.0
|
|
19
19
|
Requires-Dist: pillow>=12.3.0
|
|
20
20
|
Requires-Dist: pydantic>=2.13.5
|
|
21
|
-
Requires-Dist: python-frontmatter>=1.
|
|
21
|
+
Requires-Dist: python-frontmatter>=1.3.0
|
|
22
22
|
Requires-Dist: python-json-logger>=3.3.0
|
|
23
23
|
Requires-Dist: tenacity>=9.1.2
|
|
24
24
|
Requires-Dist: websockets>=12
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
|
+
from typing import NotRequired, TypedDict
|
|
6
|
+
|
|
7
|
+
from openhands.sdk.conversation.types import TraceMetadataValue
|
|
8
|
+
from openhands.sdk.logger import get_logger
|
|
9
|
+
from openhands.sdk.observability.laminar import (
|
|
10
|
+
default_observability_span_name_from_env,
|
|
11
|
+
observability_metadata_from_env,
|
|
12
|
+
observability_parent_span_context_from_env,
|
|
13
|
+
observability_tags_from_env,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
_AUTOMATION_ID_KEY = "automation.id"
|
|
20
|
+
_AUTOMATION_NAME_KEY = "automation.name"
|
|
21
|
+
_AUTOMATION_RUN_ID_KEY = "automation.run_id"
|
|
22
|
+
_AUTOMATION_TRIGGER_SOURCE_KEY = "automation.trigger_source"
|
|
23
|
+
_DEFAULT_AUTOMATION_SPAN_NAME = "automation.conversation"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AutomationObservabilityContext(TypedDict):
|
|
27
|
+
observability_metadata: NotRequired[dict[str, TraceMetadataValue]]
|
|
28
|
+
observability_tags: NotRequired[list[str]]
|
|
29
|
+
observability_span_name: NotRequired[str]
|
|
30
|
+
observability_parent_span_context: NotRequired[str]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AutomationConversationKwargs(AutomationObservabilityContext, total=False):
|
|
34
|
+
tags: dict[str, str]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _merge_metadata(
|
|
38
|
+
metadata: Mapping[str, TraceMetadataValue] | None,
|
|
39
|
+
) -> dict[str, TraceMetadataValue]:
|
|
40
|
+
merged = observability_metadata_from_env()
|
|
41
|
+
if metadata:
|
|
42
|
+
merged.update(dict(metadata))
|
|
43
|
+
return merged
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _merge_tags(tags: Sequence[str] | None) -> list[str]:
|
|
47
|
+
merged: list[str] = []
|
|
48
|
+
seen: set[str] = set()
|
|
49
|
+
for tag in [*observability_tags_from_env(), *(tags or [])]:
|
|
50
|
+
tag = tag.strip()
|
|
51
|
+
if tag and tag not in seen:
|
|
52
|
+
seen.add(tag)
|
|
53
|
+
merged.append(tag)
|
|
54
|
+
return merged
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _metadata_str(metadata: Mapping[str, TraceMetadataValue], key: str) -> str | None:
|
|
58
|
+
value = metadata.get(key)
|
|
59
|
+
if value is None or isinstance(value, bool | list):
|
|
60
|
+
return None
|
|
61
|
+
return str(value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def automation_observability_context(
|
|
65
|
+
*,
|
|
66
|
+
metadata: Mapping[str, TraceMetadataValue] | None = None,
|
|
67
|
+
tags: Sequence[str] | None = None,
|
|
68
|
+
span_name: str | None = None,
|
|
69
|
+
parent_span_context: str | None = None,
|
|
70
|
+
) -> AutomationObservabilityContext:
|
|
71
|
+
"""Collect generic automation observability fields for SDK conversations.
|
|
72
|
+
|
|
73
|
+
The automation service owns automation-specific values and supplies them via
|
|
74
|
+
generic ``OPENHANDS_OBSERVABILITY_*`` environment variables. This helper
|
|
75
|
+
collects those generic values and merges caller-provided additions, so custom
|
|
76
|
+
automation scripts do not need to duplicate the boilerplate for every
|
|
77
|
+
``Conversation(...)`` call.
|
|
78
|
+
"""
|
|
79
|
+
context: AutomationObservabilityContext = {}
|
|
80
|
+
|
|
81
|
+
merged_metadata = _merge_metadata(metadata)
|
|
82
|
+
if merged_metadata:
|
|
83
|
+
context["observability_metadata"] = merged_metadata
|
|
84
|
+
|
|
85
|
+
merged_tags = _merge_tags(tags)
|
|
86
|
+
if merged_tags:
|
|
87
|
+
context["observability_tags"] = merged_tags
|
|
88
|
+
|
|
89
|
+
resolved_span_name = span_name or default_observability_span_name_from_env()
|
|
90
|
+
if resolved_span_name:
|
|
91
|
+
context["observability_span_name"] = resolved_span_name
|
|
92
|
+
|
|
93
|
+
resolved_parent_context = (
|
|
94
|
+
parent_span_context or observability_parent_span_context_from_env()
|
|
95
|
+
)
|
|
96
|
+
if resolved_parent_context:
|
|
97
|
+
context["observability_parent_span_context"] = resolved_parent_context
|
|
98
|
+
|
|
99
|
+
return context
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def automation_conversation_tags(
|
|
103
|
+
metadata: Mapping[str, TraceMetadataValue] | None = None,
|
|
104
|
+
) -> dict[str, str]:
|
|
105
|
+
"""Build product conversation tags from automation observability metadata."""
|
|
106
|
+
merged_metadata = _merge_metadata(metadata)
|
|
107
|
+
tags: dict[str, str] = {}
|
|
108
|
+
|
|
109
|
+
automation_id = _metadata_str(merged_metadata, _AUTOMATION_ID_KEY)
|
|
110
|
+
automation_name = _metadata_str(merged_metadata, _AUTOMATION_NAME_KEY)
|
|
111
|
+
automation_run_id = _metadata_str(merged_metadata, _AUTOMATION_RUN_ID_KEY)
|
|
112
|
+
trigger_source = _metadata_str(merged_metadata, _AUTOMATION_TRIGGER_SOURCE_KEY)
|
|
113
|
+
|
|
114
|
+
if automation_id or automation_name or automation_run_id or trigger_source:
|
|
115
|
+
tags["automationtrigger"] = "automation"
|
|
116
|
+
if automation_id:
|
|
117
|
+
tags["automationid"] = automation_id
|
|
118
|
+
if automation_name:
|
|
119
|
+
tags["automationname"] = automation_name
|
|
120
|
+
if automation_run_id:
|
|
121
|
+
tags["automationrunid"] = automation_run_id
|
|
122
|
+
|
|
123
|
+
return tags
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def automation_conversation_kwargs(
|
|
127
|
+
*,
|
|
128
|
+
metadata: Mapping[str, TraceMetadataValue] | None = None,
|
|
129
|
+
observability_tags: Sequence[str] | None = None,
|
|
130
|
+
conversation_tags: Mapping[str, str] | None = None,
|
|
131
|
+
span_name: str | None = None,
|
|
132
|
+
parent_span_context: str | None = None,
|
|
133
|
+
include_conversation_tags: bool = True,
|
|
134
|
+
) -> AutomationConversationKwargs:
|
|
135
|
+
"""Return kwargs to pass into ``Conversation(...)`` from an automation script.
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
``Conversation(..., **automation_conversation_kwargs())``
|
|
139
|
+
"""
|
|
140
|
+
kwargs: AutomationConversationKwargs = {}
|
|
141
|
+
resolved_span_name = (
|
|
142
|
+
span_name
|
|
143
|
+
or default_observability_span_name_from_env()
|
|
144
|
+
or _DEFAULT_AUTOMATION_SPAN_NAME
|
|
145
|
+
)
|
|
146
|
+
context = automation_observability_context(
|
|
147
|
+
metadata=metadata,
|
|
148
|
+
tags=observability_tags,
|
|
149
|
+
span_name=resolved_span_name,
|
|
150
|
+
parent_span_context=parent_span_context,
|
|
151
|
+
)
|
|
152
|
+
if context_metadata := context.get("observability_metadata"):
|
|
153
|
+
kwargs["observability_metadata"] = context_metadata
|
|
154
|
+
if context_tags := context.get("observability_tags"):
|
|
155
|
+
kwargs["observability_tags"] = context_tags
|
|
156
|
+
if context_span_name := context.get("observability_span_name"):
|
|
157
|
+
kwargs["observability_span_name"] = context_span_name
|
|
158
|
+
if context_parent := context.get("observability_parent_span_context"):
|
|
159
|
+
kwargs["observability_parent_span_context"] = context_parent
|
|
160
|
+
|
|
161
|
+
if include_conversation_tags:
|
|
162
|
+
merged_conversation_tags = automation_conversation_tags(metadata)
|
|
163
|
+
if conversation_tags:
|
|
164
|
+
merged_conversation_tags.update(dict(conversation_tags))
|
|
165
|
+
if merged_conversation_tags:
|
|
166
|
+
kwargs["tags"] = merged_conversation_tags
|
|
167
|
+
|
|
168
|
+
return kwargs
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def automation_observability_headers(
|
|
172
|
+
*,
|
|
173
|
+
metadata: Mapping[str, TraceMetadataValue] | None = None,
|
|
174
|
+
tags: Sequence[str] | None = None,
|
|
175
|
+
span_name: str | None = None,
|
|
176
|
+
parent_span_context: str | None = None,
|
|
177
|
+
) -> dict[str, str]:
|
|
178
|
+
"""Return observability headers for direct agent-server API calls."""
|
|
179
|
+
context = automation_observability_context(
|
|
180
|
+
metadata=metadata,
|
|
181
|
+
tags=tags,
|
|
182
|
+
span_name=span_name,
|
|
183
|
+
parent_span_context=parent_span_context,
|
|
184
|
+
)
|
|
185
|
+
headers: dict[str, str] = {}
|
|
186
|
+
if context_metadata := context.get("observability_metadata"):
|
|
187
|
+
headers["X-OpenHands-Observability-Metadata"] = json.dumps(
|
|
188
|
+
context_metadata, separators=(",", ":")
|
|
189
|
+
)
|
|
190
|
+
if context_tags := context.get("observability_tags"):
|
|
191
|
+
headers["X-OpenHands-Observability-Tags"] = ",".join(context_tags)
|
|
192
|
+
if context_span_name := context.get("observability_span_name"):
|
|
193
|
+
headers["X-OpenHands-Observability-Span-Name"] = context_span_name
|
|
194
|
+
if context_parent := context.get("observability_parent_span_context"):
|
|
195
|
+
headers["X-OpenHands-Observability-Parent-Span-Context"] = context_parent
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
from opentelemetry.propagate import inject
|
|
199
|
+
|
|
200
|
+
inject(headers)
|
|
201
|
+
except Exception:
|
|
202
|
+
logger.debug(
|
|
203
|
+
"Failed to inject OpenTelemetry propagation headers", exc_info=True
|
|
204
|
+
)
|
|
205
|
+
return headers
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
__all__ = [
|
|
209
|
+
"AutomationConversationKwargs",
|
|
210
|
+
"AutomationObservabilityContext",
|
|
211
|
+
"automation_conversation_kwargs",
|
|
212
|
+
"automation_conversation_tags",
|
|
213
|
+
"automation_observability_context",
|
|
214
|
+
"automation_observability_headers",
|
|
215
|
+
]
|
|
@@ -18,7 +18,10 @@ from openhands.sdk.llm.llm import LLM
|
|
|
18
18
|
from openhands.sdk.llm.message import Message
|
|
19
19
|
from openhands.sdk.observability.laminar import (
|
|
20
20
|
RootSpan,
|
|
21
|
+
default_observability_span_name_from_env,
|
|
21
22
|
end_root_span,
|
|
23
|
+
merge_observability_metadata,
|
|
24
|
+
observability_parent_span_context_from_env,
|
|
22
25
|
should_enable_observability,
|
|
23
26
|
start_child_span,
|
|
24
27
|
start_root_span,
|
|
@@ -147,6 +150,7 @@ class BaseConversation(ABC):
|
|
|
147
150
|
metadata: dict[str, TraceMetadataValue] | None = None,
|
|
148
151
|
tags: list[str] | None = None,
|
|
149
152
|
conversation_tags: Mapping[str, str] | None = None,
|
|
153
|
+
parent_span_context: str | None = None,
|
|
150
154
|
) -> None:
|
|
151
155
|
"""Start a per-conversation observability root span.
|
|
152
156
|
|
|
@@ -163,13 +167,24 @@ class BaseConversation(ABC):
|
|
|
163
167
|
if self._observability_root_span is not None:
|
|
164
168
|
# Idempotent: never start two roots for one conversation.
|
|
165
169
|
return
|
|
170
|
+
default_span_name = default_observability_span_name_from_env()
|
|
171
|
+
if default_span_name and span_name == "conversation":
|
|
172
|
+
span_name = default_span_name
|
|
173
|
+
|
|
174
|
+
metadata = merge_observability_metadata(metadata)
|
|
175
|
+
attributes: dict[str, TraceMetadataValue] = dict(metadata)
|
|
176
|
+
tag_attributes = _conversation_tag_attributes(conversation_tags)
|
|
177
|
+
if tag_attributes:
|
|
178
|
+
attributes.update(tag_attributes)
|
|
166
179
|
self._observability_root_span = start_root_span(
|
|
167
180
|
"conversation",
|
|
168
181
|
session_id=session_id,
|
|
169
182
|
user_id=user_id,
|
|
170
183
|
metadata=metadata,
|
|
171
184
|
tags=tags,
|
|
172
|
-
attributes=
|
|
185
|
+
attributes=attributes,
|
|
186
|
+
parent_span_context=parent_span_context
|
|
187
|
+
or observability_parent_span_context_from_env(),
|
|
173
188
|
)
|
|
174
189
|
if span_name != "conversation":
|
|
175
190
|
start_child_span(self._observability_root_span, span_name, tags=tags)
|
|
@@ -88,6 +88,7 @@ class Conversation:
|
|
|
88
88
|
observability_metadata: dict[str, TraceMetadataValue] | None = None,
|
|
89
89
|
observability_tags: list[str] | None = None,
|
|
90
90
|
observability_span_name: str = "conversation",
|
|
91
|
+
observability_parent_span_context: str | None = None,
|
|
91
92
|
) -> "LocalConversation": ...
|
|
92
93
|
|
|
93
94
|
@overload
|
|
@@ -117,6 +118,7 @@ class Conversation:
|
|
|
117
118
|
observability_metadata: dict[str, TraceMetadataValue] | None = None,
|
|
118
119
|
observability_tags: list[str] | None = None,
|
|
119
120
|
observability_span_name: str = "conversation",
|
|
121
|
+
observability_parent_span_context: str | None = None,
|
|
120
122
|
) -> "RemoteConversation": ...
|
|
121
123
|
|
|
122
124
|
def __new__(
|
|
@@ -146,6 +148,7 @@ class Conversation:
|
|
|
146
148
|
observability_metadata: dict[str, TraceMetadataValue] | None = None,
|
|
147
149
|
observability_tags: list[str] | None = None,
|
|
148
150
|
observability_span_name: str = "conversation",
|
|
151
|
+
observability_parent_span_context: str | None = None,
|
|
149
152
|
) -> BaseConversation:
|
|
150
153
|
from openhands.sdk.conversation.impl.local_conversation import LocalConversation
|
|
151
154
|
from openhands.sdk.conversation.impl.remote_conversation import (
|
|
@@ -209,6 +212,7 @@ class Conversation:
|
|
|
209
212
|
observability_metadata=observability_metadata,
|
|
210
213
|
observability_tags=observability_tags,
|
|
211
214
|
observability_span_name=observability_span_name,
|
|
215
|
+
observability_parent_span_context=observability_parent_span_context,
|
|
212
216
|
)
|
|
213
217
|
|
|
214
218
|
return LocalConversation(
|
|
@@ -232,4 +236,5 @@ class Conversation:
|
|
|
232
236
|
observability_metadata=observability_metadata,
|
|
233
237
|
observability_tags=observability_tags,
|
|
234
238
|
observability_span_name=observability_span_name,
|
|
239
|
+
observability_parent_span_context=observability_parent_span_context,
|
|
235
240
|
)
|
{openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/local_conversation.py
RENAMED
|
@@ -236,6 +236,7 @@ class LocalConversation(BaseConversation):
|
|
|
236
236
|
# positional argument.
|
|
237
237
|
max_budget_per_run: float | None = None,
|
|
238
238
|
observability_span_name: str = "conversation",
|
|
239
|
+
observability_parent_span_context: str | None = None,
|
|
239
240
|
prompt_cache_key: str | None = None,
|
|
240
241
|
file_store: FileStore | None = None,
|
|
241
242
|
mcp_tool_provider: MCPToolProvider | None = None,
|
|
@@ -552,6 +553,7 @@ class LocalConversation(BaseConversation):
|
|
|
552
553
|
metadata=observability_metadata,
|
|
553
554
|
tags=observability_tags,
|
|
554
555
|
conversation_tags=tags,
|
|
556
|
+
parent_span_context=observability_parent_span_context,
|
|
555
557
|
)
|
|
556
558
|
self.delete_on_close = delete_on_close
|
|
557
559
|
|
{openhands_sdk-1.51.0 → openhands_sdk-1.52.0}/openhands/sdk/conversation/impl/remote_conversation.py
RENAMED
|
@@ -53,7 +53,13 @@ from openhands.sdk.event.types import EventID
|
|
|
53
53
|
from openhands.sdk.hooks import HookConfig
|
|
54
54
|
from openhands.sdk.llm import LLM, Message, TextContent
|
|
55
55
|
from openhands.sdk.logger import DEBUG, get_logger
|
|
56
|
-
from openhands.sdk.observability.laminar import
|
|
56
|
+
from openhands.sdk.observability.laminar import (
|
|
57
|
+
OPERATION_METADATA_KEY,
|
|
58
|
+
default_observability_span_name_from_env,
|
|
59
|
+
merge_observability_metadata,
|
|
60
|
+
observability_parent_span_context_from_env,
|
|
61
|
+
observe,
|
|
62
|
+
)
|
|
57
63
|
from openhands.sdk.security.analyzer import SecurityAnalyzerBase
|
|
58
64
|
from openhands.sdk.security.confirmation_policy import (
|
|
59
65
|
ConfirmationPolicyBase,
|
|
@@ -739,6 +745,7 @@ class RemoteConversation(BaseConversation):
|
|
|
739
745
|
observability_metadata: dict[str, TraceMetadataValue] | None = None,
|
|
740
746
|
observability_tags: list[str] | None = None,
|
|
741
747
|
observability_span_name: str = "conversation",
|
|
748
|
+
observability_parent_span_context: str | None = None,
|
|
742
749
|
**_: object,
|
|
743
750
|
) -> None:
|
|
744
751
|
"""Remote conversation proxy that talks to an agent server.
|
|
@@ -777,6 +784,15 @@ class RemoteConversation(BaseConversation):
|
|
|
777
784
|
observability_span_name: Optional child span name for observability
|
|
778
785
|
backends. The root span remains named "conversation".
|
|
779
786
|
"""
|
|
787
|
+
observability_metadata = merge_observability_metadata(observability_metadata)
|
|
788
|
+
observability_parent_span_context = (
|
|
789
|
+
observability_parent_span_context
|
|
790
|
+
or observability_parent_span_context_from_env()
|
|
791
|
+
)
|
|
792
|
+
default_span_name = default_observability_span_name_from_env()
|
|
793
|
+
if default_span_name and observability_span_name == "conversation":
|
|
794
|
+
observability_span_name = default_span_name
|
|
795
|
+
|
|
780
796
|
# Client tool specs the server already has persisted for this
|
|
781
797
|
# conversation (populated when re-attaching to an existing one). These
|
|
782
798
|
# must be registered locally before the initial event sync so that
|
|
@@ -860,6 +876,7 @@ class RemoteConversation(BaseConversation):
|
|
|
860
876
|
if observability_tags is not None
|
|
861
877
|
else [],
|
|
862
878
|
"observability_span_name": observability_span_name,
|
|
879
|
+
"observability_parent_span_context": observability_parent_span_context,
|
|
863
880
|
"user_id": user_id,
|
|
864
881
|
}
|
|
865
882
|
if user_id:
|
|
@@ -921,6 +938,7 @@ class RemoteConversation(BaseConversation):
|
|
|
921
938
|
metadata=observability_metadata,
|
|
922
939
|
tags=observability_tags,
|
|
923
940
|
conversation_tags=tags,
|
|
941
|
+
parent_span_context=observability_parent_span_context,
|
|
924
942
|
)
|
|
925
943
|
# All hooks (including SessionStart/SessionEnd) are executed server-side.
|
|
926
944
|
# hook_config is sent in the creation payload.
|
|
@@ -944,6 +962,22 @@ class RemoteConversation(BaseConversation):
|
|
|
944
962
|
conversation ID follows the server's idempotency contract; this method
|
|
945
963
|
does not probe for an existing conversation.
|
|
946
964
|
"""
|
|
965
|
+
updates: dict[str, object] = {
|
|
966
|
+
"observability_metadata": merge_observability_metadata(
|
|
967
|
+
request.observability_metadata
|
|
968
|
+
),
|
|
969
|
+
}
|
|
970
|
+
default_span_name = default_observability_span_name_from_env()
|
|
971
|
+
if default_span_name and request.observability_span_name == "conversation":
|
|
972
|
+
updates["observability_span_name"] = default_span_name
|
|
973
|
+
parent_span_context = (
|
|
974
|
+
request.observability_parent_span_context
|
|
975
|
+
or observability_parent_span_context_from_env()
|
|
976
|
+
)
|
|
977
|
+
if parent_span_context:
|
|
978
|
+
updates["observability_parent_span_context"] = parent_span_context
|
|
979
|
+
request = request.model_copy(update=updates)
|
|
980
|
+
|
|
947
981
|
response = _send_request(
|
|
948
982
|
workspace.client,
|
|
949
983
|
"POST",
|
|
@@ -962,6 +996,7 @@ class RemoteConversation(BaseConversation):
|
|
|
962
996
|
metadata=request.observability_metadata,
|
|
963
997
|
tags=request.observability_tags,
|
|
964
998
|
conversation_tags=request.tags,
|
|
999
|
+
parent_span_context=request.observability_parent_span_context,
|
|
965
1000
|
)
|
|
966
1001
|
return conversation
|
|
967
1002
|
|
|
@@ -258,6 +258,14 @@ class ConversationConfig(BaseModel):
|
|
|
258
258
|
"span names for grouping or signal routing."
|
|
259
259
|
),
|
|
260
260
|
)
|
|
261
|
+
observability_parent_span_context: str | None = Field(
|
|
262
|
+
default=None,
|
|
263
|
+
description=(
|
|
264
|
+
"Serialized parent span context used to attach this conversation to "
|
|
265
|
+
"an upstream automation or integration trace."
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
|
|
261
269
|
autotitle: bool = Field(
|
|
262
270
|
default=True,
|
|
263
271
|
description=(
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import functools
|
|
5
5
|
import inspect
|
|
6
|
+
import json
|
|
6
7
|
import sys
|
|
7
8
|
from collections.abc import Callable, Iterator, Mapping
|
|
8
9
|
from typing import TYPE_CHECKING, Any, Final, Literal, cast
|
|
@@ -35,6 +36,109 @@ _OBSERVABILITY_ENV_KEYS: Final[tuple[str, ...]] = (
|
|
|
35
36
|
OPERATION_METADATA_KEY: Final[str] = "openhands.operation"
|
|
36
37
|
"""Metadata key naming the side-utility operation a span subtree belongs to."""
|
|
37
38
|
|
|
39
|
+
_OBSERVABILITY_METADATA_ENV: Final[str] = "OPENHANDS_OBSERVABILITY_METADATA"
|
|
40
|
+
_OBSERVABILITY_TAGS_ENV: Final[str] = "OPENHANDS_OBSERVABILITY_TAGS"
|
|
41
|
+
_OBSERVABILITY_SPAN_NAME_ENV: Final[str] = "OPENHANDS_OBSERVABILITY_SPAN_NAME"
|
|
42
|
+
_OBSERVABILITY_PARENT_CONTEXT_ENV: Final[str] = (
|
|
43
|
+
"OPENHANDS_OBSERVABILITY_PARENT_SPAN_CONTEXT"
|
|
44
|
+
)
|
|
45
|
+
_LAMINAR_PARENT_CONTEXT_ENV: Final[str] = "LMNR_SPAN_CONTEXT"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_trace_metadata_value(value: Any) -> bool:
|
|
49
|
+
if isinstance(value, str | bool | int | float):
|
|
50
|
+
return True
|
|
51
|
+
if isinstance(value, list):
|
|
52
|
+
return (
|
|
53
|
+
all(isinstance(item, str) for item in value)
|
|
54
|
+
or all(isinstance(item, bool) for item in value)
|
|
55
|
+
or all(
|
|
56
|
+
isinstance(item, int) and not isinstance(item, bool) for item in value
|
|
57
|
+
)
|
|
58
|
+
or all(isinstance(item, float) for item in value)
|
|
59
|
+
)
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _clean_trace_metadata(value: Any) -> dict[str, TraceMetadataValue]:
|
|
64
|
+
if not isinstance(value, dict):
|
|
65
|
+
return {}
|
|
66
|
+
cleaned: dict[str, TraceMetadataValue] = {}
|
|
67
|
+
for key, item in value.items():
|
|
68
|
+
if isinstance(key, str) and key and _is_trace_metadata_value(item):
|
|
69
|
+
cleaned[key] = item
|
|
70
|
+
return cleaned
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def observability_metadata_from_env() -> dict[str, TraceMetadataValue]:
|
|
74
|
+
"""Return generic observability metadata supplied by the process env."""
|
|
75
|
+
raw_metadata = get_env(_OBSERVABILITY_METADATA_ENV)
|
|
76
|
+
if not raw_metadata:
|
|
77
|
+
return {}
|
|
78
|
+
try:
|
|
79
|
+
return _clean_trace_metadata(json.loads(raw_metadata))
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
logger.debug("Ignoring invalid %s", _OBSERVABILITY_METADATA_ENV)
|
|
82
|
+
return {}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def merge_observability_metadata(
|
|
86
|
+
metadata: Mapping[str, TraceMetadataValue] | None,
|
|
87
|
+
) -> dict[str, TraceMetadataValue]:
|
|
88
|
+
"""Merge generic env defaults with caller-supplied metadata."""
|
|
89
|
+
merged = observability_metadata_from_env()
|
|
90
|
+
if metadata:
|
|
91
|
+
merged.update(metadata)
|
|
92
|
+
return merged
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def observability_parent_span_context_from_env() -> str | None:
|
|
96
|
+
"""Return serialized parent span context propagated by the host process."""
|
|
97
|
+
return get_env(_OBSERVABILITY_PARENT_CONTEXT_ENV) or get_env(
|
|
98
|
+
_LAMINAR_PARENT_CONTEXT_ENV
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def default_observability_span_name_from_env() -> str | None:
|
|
103
|
+
"""Return the default conversation child span name from generic env."""
|
|
104
|
+
return get_env(_OBSERVABILITY_SPAN_NAME_ENV)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def observability_tags_from_env() -> list[str]:
|
|
108
|
+
"""Return comma-separated observability tags supplied by generic env."""
|
|
109
|
+
raw_tags = get_env(_OBSERVABILITY_TAGS_ENV)
|
|
110
|
+
if not raw_tags:
|
|
111
|
+
return []
|
|
112
|
+
return [tag.strip() for tag in raw_tags.split(",") if tag.strip()]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def observability_headers_from_env() -> dict[str, str]:
|
|
116
|
+
"""Build generic observability headers for agent-server API calls."""
|
|
117
|
+
headers: dict[str, str] = {}
|
|
118
|
+
metadata = observability_metadata_from_env()
|
|
119
|
+
if metadata:
|
|
120
|
+
headers["X-OpenHands-Observability-Metadata"] = json.dumps(
|
|
121
|
+
metadata, separators=(",", ":")
|
|
122
|
+
)
|
|
123
|
+
tags = observability_tags_from_env()
|
|
124
|
+
if tags:
|
|
125
|
+
headers["X-OpenHands-Observability-Tags"] = ",".join(tags)
|
|
126
|
+
span_name = default_observability_span_name_from_env()
|
|
127
|
+
if span_name:
|
|
128
|
+
headers["X-OpenHands-Observability-Span-Name"] = span_name
|
|
129
|
+
parent_context = observability_parent_span_context_from_env()
|
|
130
|
+
if parent_context:
|
|
131
|
+
headers["X-OpenHands-Observability-Parent-Span-Context"] = parent_context
|
|
132
|
+
try:
|
|
133
|
+
from opentelemetry.propagate import inject
|
|
134
|
+
|
|
135
|
+
inject(headers)
|
|
136
|
+
except Exception:
|
|
137
|
+
logger.debug(
|
|
138
|
+
"Failed to inject OpenTelemetry propagation headers", exc_info=True
|
|
139
|
+
)
|
|
140
|
+
return headers
|
|
141
|
+
|
|
38
142
|
|
|
39
143
|
def _get_int_env(key: str) -> int | None:
|
|
40
144
|
"""Read an environment variable as an optional int."""
|
|
@@ -300,15 +404,24 @@ class RootSpan:
|
|
|
300
404
|
name: str,
|
|
301
405
|
session_id: str | None = None,
|
|
302
406
|
user_id: str | None = None,
|
|
303
|
-
attributes: Mapping[str,
|
|
407
|
+
attributes: Mapping[str, TraceMetadataValue] | None = None,
|
|
304
408
|
metadata: dict[str, TraceMetadataValue] | None = None,
|
|
305
409
|
tags: list[str] | None = None,
|
|
410
|
+
parent_span_context: str | None = None,
|
|
306
411
|
) -> None:
|
|
307
412
|
from lmnr import Laminar
|
|
308
413
|
|
|
414
|
+
parent = None
|
|
415
|
+
if parent_span_context:
|
|
416
|
+
with contextlib.suppress(Exception):
|
|
417
|
+
parent = Laminar.deserialize_span_context(parent_span_context)
|
|
418
|
+
|
|
309
419
|
# ``start_span`` returns a span without attaching it as the current
|
|
310
420
|
# OTel context; we'll restore it on every entry point via ``use_span``.
|
|
311
|
-
|
|
421
|
+
if parent is not None:
|
|
422
|
+
self.span = Laminar.start_span(name, parent_span_context=parent)
|
|
423
|
+
else:
|
|
424
|
+
self.span = Laminar.start_span(name)
|
|
312
425
|
if attributes:
|
|
313
426
|
with contextlib.suppress(Exception):
|
|
314
427
|
for key, value in attributes.items():
|
|
@@ -350,9 +463,10 @@ def start_root_span(
|
|
|
350
463
|
name: str,
|
|
351
464
|
session_id: str | None = None,
|
|
352
465
|
user_id: str | None = None,
|
|
353
|
-
attributes: Mapping[str,
|
|
466
|
+
attributes: Mapping[str, TraceMetadataValue] | None = None,
|
|
354
467
|
metadata: dict[str, TraceMetadataValue] | None = None,
|
|
355
468
|
tags: list[str] | None = None,
|
|
469
|
+
parent_span_context: str | None = None,
|
|
356
470
|
) -> RootSpan | None:
|
|
357
471
|
"""Create a long-lived root span for an owning object.
|
|
358
472
|
|
|
@@ -368,6 +482,7 @@ def start_root_span(
|
|
|
368
482
|
attributes=attributes,
|
|
369
483
|
metadata=metadata,
|
|
370
484
|
tags=tags,
|
|
485
|
+
parent_span_context=parent_span_context,
|
|
371
486
|
)
|
|
372
487
|
except Exception:
|
|
373
488
|
logger.debug("Failed to create observability root span", exc_info=True)
|
|
@@ -10,6 +10,7 @@ from openhands.sdk.conversation.exceptions import ConversationRunError
|
|
|
10
10
|
from openhands.sdk.event.conversation_error import ConversationErrorEvent
|
|
11
11
|
from openhands.sdk.git.models import GitChange, GitDiff
|
|
12
12
|
from openhands.sdk.logger import get_logger
|
|
13
|
+
from openhands.sdk.observability.laminar import observability_headers_from_env
|
|
13
14
|
from openhands.sdk.utils.models import DiscriminatedUnionMixin
|
|
14
15
|
from openhands.sdk.workspace.models import CommandResult, FileOperationResult
|
|
15
16
|
|
|
@@ -145,7 +146,7 @@ class BaseWorkspace(DiscriminatedUnionMixin, ABC):
|
|
|
145
146
|
payload["cost"] = self._accumulated_cost
|
|
146
147
|
|
|
147
148
|
try:
|
|
148
|
-
headers
|
|
149
|
+
headers = observability_headers_from_env()
|
|
149
150
|
if callback_api_key:
|
|
150
151
|
headers["Authorization"] = f"Bearer {callback_api_key}"
|
|
151
152
|
with httpx.Client(timeout=10.0) as cb_client:
|
|
@@ -89,6 +89,14 @@ class AsyncRemoteWorkspace(RemoteWorkspaceMixin):
|
|
|
89
89
|
"""Read the latest output; a missing exit code means it is still running."""
|
|
90
90
|
return await self._execute(self._get_command_output_generator(command_id))
|
|
91
91
|
|
|
92
|
+
async def stop_command(self, command_id: str) -> None:
|
|
93
|
+
"""Stop a running bash command by id.
|
|
94
|
+
|
|
95
|
+
Unknown or already-finished ids are no-ops, matching the server's
|
|
96
|
+
idempotency contract.
|
|
97
|
+
"""
|
|
98
|
+
await self._execute(self._stop_command_generator(command_id))
|
|
99
|
+
|
|
92
100
|
async def get_runtime_session_key(self) -> str:
|
|
93
101
|
"""Get the scoped worker credential for this conversation runtime."""
|
|
94
102
|
return await self._execute(self._runtime_lifecycle_generator(release=False))
|
|
@@ -145,6 +145,14 @@ class RemoteWorkspace(RemoteWorkspaceMixin, BaseWorkspace):
|
|
|
145
145
|
"""Read the latest output; a missing exit code means it is still running."""
|
|
146
146
|
return self._execute(self._get_command_output_generator(command_id))
|
|
147
147
|
|
|
148
|
+
def stop_command(self, command_id: str) -> None:
|
|
149
|
+
"""Stop a running bash command by id.
|
|
150
|
+
|
|
151
|
+
Unknown or already-finished ids are no-ops, matching the server's
|
|
152
|
+
idempotency contract.
|
|
153
|
+
"""
|
|
154
|
+
self._execute(self._stop_command_generator(command_id))
|
|
155
|
+
|
|
148
156
|
def get_runtime_session_key(self) -> str:
|
|
149
157
|
"""Get the scoped worker credential for this conversation runtime."""
|
|
150
158
|
return self._execute(self._runtime_lifecycle_generator(release=False))
|
|
@@ -9,6 +9,7 @@ import httpx
|
|
|
9
9
|
from pydantic import BaseModel, Field, TypeAdapter
|
|
10
10
|
|
|
11
11
|
from openhands.sdk.git.models import GitChange, GitDiff
|
|
12
|
+
from openhands.sdk.observability.laminar import observability_headers_from_env
|
|
12
13
|
from openhands.sdk.utils.path import to_posix_path
|
|
13
14
|
from openhands.sdk.workspace.models import CommandResult, FileOperationResult
|
|
14
15
|
|
|
@@ -73,7 +74,7 @@ class RemoteWorkspaceMixin(BaseModel):
|
|
|
73
74
|
|
|
74
75
|
@property
|
|
75
76
|
def _headers(self):
|
|
76
|
-
headers =
|
|
77
|
+
headers = observability_headers_from_env()
|
|
77
78
|
if self.api_key:
|
|
78
79
|
headers["X-Session-API-Key"] = self.api_key
|
|
79
80
|
return headers
|
|
@@ -133,6 +134,21 @@ class RemoteWorkspaceMixin(BaseModel):
|
|
|
133
134
|
page = yield from self._search_command_output_generator(command_id)
|
|
134
135
|
return next(iter(page.get("items", [])), None)
|
|
135
136
|
|
|
137
|
+
def _stop_command_generator(
|
|
138
|
+
self,
|
|
139
|
+
command_id: str,
|
|
140
|
+
) -> Generator[dict[str, Any], httpx.Response]:
|
|
141
|
+
response = yield {
|
|
142
|
+
"method": "POST",
|
|
143
|
+
"url": f"{self.host}{self.api_prefix}/bash/bash_commands/{command_id}/stop",
|
|
144
|
+
"headers": self._headers,
|
|
145
|
+
"timeout": 60,
|
|
146
|
+
}
|
|
147
|
+
if response.status_code == 404:
|
|
148
|
+
return None
|
|
149
|
+
response.raise_for_status()
|
|
150
|
+
return None
|
|
151
|
+
|
|
136
152
|
def _runtime_lifecycle_generator(
|
|
137
153
|
self,
|
|
138
154
|
*,
|