openhands-sdk 1.55.0__tar.gz → 1.56.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.55.0 → openhands_sdk-1.56.0}/PKG-INFO +2 -2
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/base.py +4 -21
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/impl/local_conversation.py +33 -5
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/impl/remote_conversation.py +70 -3
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/request.py +48 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/git/git_changes.py +31 -6
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/llm_profile_store.py +7 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/meta_profile_store.py +4 -5
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/unverified_models.py +16 -3
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/marketplace/registry.py +20 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/marketplace/types.py +55 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/__init__.py +8 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/fetch.py +34 -1
- openhands_sdk-1.56.0/openhands/sdk/plugin/paths.py +17 -0
- openhands_sdk-1.56.0/openhands/sdk/plugin/strict_fetch.py +504 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/profiles/__init__.py +4 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/profiles/agent_profile.py +35 -8
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/profiles/profile_refs.py +7 -2
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/settings/model.py +30 -68
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/installed.py +6 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/defaults.py +0 -18
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/redact.py +3 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands_sdk.egg-info/PKG-INFO +2 -2
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands_sdk.egg-info/SOURCES.txt +4 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands_sdk.egg-info/requires.txt +1 -1
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/pyproject.toml +2 -2
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/acp_agent.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/acp_contracts.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/acp_model_discovery.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/acp_models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/acp_tracing.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/agent.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/critic_mixin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/parallel_executor.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/response_dispatch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/stream_context.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/agent/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/automation/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/banner.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/agent_context.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/condenser/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/memory.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/presets.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/section.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/sections/static.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/properties/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/context/view/view.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/cancellation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/conversation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/goal/controller.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/goal/judge.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/goal/runner.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/message_request.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/state.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/credential.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/api/client.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/critic/result.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/acp_tool_call.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/conversation_state.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/error_classification.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/hook_execution.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/resume_transcript.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/security.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/streaming_delta.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/fetch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/installation/info.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/installation/interface.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/installation/manager.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/extensions/installation/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/git/cached_repo.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/git/git_commits.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/hooks/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/hooks/config.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/hooks/executor.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/hooks/manager.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/hooks/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/io/cache.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/launch/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/launch/errors.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/launch/finalize.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/launch/preview.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/launch/resolve.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/_message_normalization.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/_response_stream.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/_tokenizer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/auth/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/auth/credentials.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/auth/openai.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/call_context.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/llm_registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/options/chat_options.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/options/responses_options.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/router/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/streaming.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/model_features.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/logger/logger.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/marketplace/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/marketplace/registration.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/config.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/oauth.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/discovery.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/format/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/format/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/installed.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/loader.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/plugin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/source.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/plugin/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/profiles/resolver.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/profiles/seed.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/secret/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/secret/secrets.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/_shell_ast.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/ensemble.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/grayswan/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/shell_parser.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/settings/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/settings/acp_install_catalog.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/settings/acp_providers.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/settings/api_models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/settings/metadata.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/exceptions.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/execute.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/fetch.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/skill.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/trigger.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/types.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/skills/utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/subagent/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/subagent/load.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/subagent/registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/subagent/schema.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/subagent/scope.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/testing/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/testing/test_llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/client_tool.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/tool/tool.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/datetime.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/files.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/masking.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/paging.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/path.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/truncate.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/models.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/remote/base.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/repo.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.55.0 → openhands_sdk-1.56.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.56.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
|
|
@@ -15,7 +15,7 @@ Requires-Dist: filelock>=3.20.1
|
|
|
15
15
|
Requires-Dist: httpx[socks]>=0.28.1
|
|
16
16
|
Requires-Dist: jsonschema>=4.23.0
|
|
17
17
|
Requires-Dist: joserfc>=1.7.5
|
|
18
|
-
Requires-Dist: litellm
|
|
18
|
+
Requires-Dist: litellm<1.106,>=1.93.0
|
|
19
19
|
Requires-Dist: pillow>=12.3.0
|
|
20
20
|
Requires-Dist: pydantic>=2.13.5
|
|
21
21
|
Requires-Dist: python-frontmatter>=1.3.0
|
|
@@ -33,7 +33,6 @@ from openhands.sdk.mcp.client import MCPClient
|
|
|
33
33
|
from openhands.sdk.mcp.config import MCPServer
|
|
34
34
|
from openhands.sdk.mcp.tool import MCPToolDefinition, MCPToolExecutor
|
|
35
35
|
from openhands.sdk.tool import (
|
|
36
|
-
BROWSER_TOOL_NAME,
|
|
37
36
|
BUILT_IN_TOOL_CLASSES,
|
|
38
37
|
BUILT_IN_TOOLS,
|
|
39
38
|
Tool,
|
|
@@ -45,7 +44,6 @@ from openhands.sdk.tool.builtins.vision_inspect import (
|
|
|
45
44
|
VisionInspectTool,
|
|
46
45
|
has_vision_profile_available,
|
|
47
46
|
)
|
|
48
|
-
from openhands.sdk.utils.deprecation import warn_deprecated
|
|
49
47
|
from openhands.sdk.utils.models import DiscriminatedUnionMixin
|
|
50
48
|
from openhands.sdk.utils.path import get_user_persistence_dir
|
|
51
49
|
|
|
@@ -268,21 +266,11 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
268
266
|
|
|
269
267
|
@field_validator("system_prompt_kwargs")
|
|
270
268
|
@classmethod
|
|
271
|
-
def
|
|
269
|
+
def _drop_retired_enable_browser(
|
|
272
270
|
cls, value: dict[str, object]
|
|
273
271
|
) -> dict[str, object]:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"system_prompt_kwargs['enable_browser']",
|
|
277
|
-
deprecated_in="1.51.0",
|
|
278
|
-
removed_in="1.56.0",
|
|
279
|
-
details=(
|
|
280
|
-
"Browser guidance now comes from the loaded browser tools; "
|
|
281
|
-
"only custom Jinja templates still read this kwarg."
|
|
282
|
-
),
|
|
283
|
-
stacklevel=5,
|
|
284
|
-
)
|
|
285
|
-
return value
|
|
272
|
+
"""Old saved conversations may still carry this retired prompt flag."""
|
|
273
|
+
return {key: item for key, item in value.items() if key != "enable_browser"}
|
|
286
274
|
|
|
287
275
|
condenser: CondenserBase | None = Field(
|
|
288
276
|
default=None,
|
|
@@ -378,15 +366,10 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
378
366
|
# own Jinja template; everything else (incl. custom policies) uses the registry.
|
|
379
367
|
preset = self._prompt_preset
|
|
380
368
|
if preset is None:
|
|
381
|
-
# Deprecated kwarg, kept until 1.56.0 for templates that branch on it.
|
|
382
|
-
template_kwargs = {
|
|
383
|
-
"enable_browser": any(t.name == BROWSER_TOOL_NAME for t in self.tools),
|
|
384
|
-
**self._resolved_template_kwargs(),
|
|
385
|
-
}
|
|
386
369
|
return render_template(
|
|
387
370
|
prompt_dir=self.prompt_dir,
|
|
388
371
|
template_name=self.system_prompt_filename,
|
|
389
|
-
**
|
|
372
|
+
**self._resolved_template_kwargs(),
|
|
390
373
|
)
|
|
391
374
|
|
|
392
375
|
return create_registry(preset).build(self._build_prompt_context()).static
|
{openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/impl/local_conversation.py
RENAMED
|
@@ -177,6 +177,16 @@ def _copy_event_for_fork(event: Event) -> Event:
|
|
|
177
177
|
return Event.model_validate_json(event.model_dump_json(exclude_none=True))
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
def _same_llm_config(a: LLM, b: LLM) -> bool:
|
|
181
|
+
"""Return whether two LLMs serialize to the same config, secrets included."""
|
|
182
|
+
if a is b:
|
|
183
|
+
return True
|
|
184
|
+
context = {"expose_secrets": True}
|
|
185
|
+
return a.model_dump(mode="json", context=context) == b.model_dump(
|
|
186
|
+
mode="json", context=context
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
180
190
|
class LocalConversation(BaseConversation):
|
|
181
191
|
agent: AgentBase
|
|
182
192
|
workspace: LocalWorkspace
|
|
@@ -1664,18 +1674,31 @@ class LocalConversation(BaseConversation):
|
|
|
1664
1674
|
"""Swap the agent's LLM to the given object.
|
|
1665
1675
|
|
|
1666
1676
|
The caller owns ``llm.usage_id``; it is the registry key. If an
|
|
1667
|
-
entry with that key already exists
|
|
1668
|
-
|
|
1669
|
-
|
|
1677
|
+
entry with that key already exists and has the same config, the
|
|
1678
|
+
cached LLM is reused. If its config differs (e.g. ``default``, which
|
|
1679
|
+
agent initialization registers, posted with another model), the
|
|
1680
|
+
passed ``llm`` replaces the entry and takes over the metrics already
|
|
1681
|
+
recorded under that ``usage_id``.
|
|
1670
1682
|
|
|
1671
1683
|
Args:
|
|
1672
1684
|
llm: LLM to install on the agent.
|
|
1673
1685
|
"""
|
|
1686
|
+
usage_id = llm.usage_id
|
|
1674
1687
|
try:
|
|
1675
|
-
|
|
1688
|
+
registered = self.llm_registry.get(usage_id)
|
|
1676
1689
|
except KeyError:
|
|
1690
|
+
registered = None
|
|
1691
|
+
if registered is not None and _same_llm_config(registered, llm):
|
|
1692
|
+
new_llm = registered
|
|
1693
|
+
else:
|
|
1677
1694
|
new_llm = create_subscription_llm_from_config(llm)
|
|
1695
|
+
if registered is not None:
|
|
1696
|
+
self.llm_registry.remove(usage_id)
|
|
1678
1697
|
self.llm_registry.add(new_llm)
|
|
1698
|
+
if registered is not None:
|
|
1699
|
+
# Keep one metrics object per usage_id: ConversationStats
|
|
1700
|
+
# tracks the registered LLM's, so accrued usage is retained.
|
|
1701
|
+
new_llm.restore_metrics(registered.metrics)
|
|
1679
1702
|
# A switch_llm tool runs on a worker thread while run()/arun() holds the
|
|
1680
1703
|
# state lock across the agent step on another thread, blocked awaiting
|
|
1681
1704
|
# this very tool. Re-acquiring _state here would deadlock, so skip it:
|
|
@@ -1760,7 +1783,8 @@ class LocalConversation(BaseConversation):
|
|
|
1760
1783
|
|
|
1761
1784
|
Raises:
|
|
1762
1785
|
ValueError: If the conversation's agent is not an :class:`ACPAgent`,
|
|
1763
|
-
|
|
1786
|
+
if ``model`` is empty or whitespace-only (live or not), or
|
|
1787
|
+
(for a live switch) the provider does not support runtime
|
|
1764
1788
|
model switching, or the ACP server rejects the switch.
|
|
1765
1789
|
TimeoutError: If a live switch's ``session/set_model`` round-trip
|
|
1766
1790
|
exceeds ``acp_prompt_timeout`` seconds.
|
|
@@ -1769,6 +1793,10 @@ class LocalConversation(BaseConversation):
|
|
|
1769
1793
|
raise ValueError(
|
|
1770
1794
|
"switch_acp_model is only supported for ACP conversations."
|
|
1771
1795
|
)
|
|
1796
|
+
# Check here, not only in set_acp_model: the pre-run path below skips
|
|
1797
|
+
# that call and would persist a blank model unchecked.
|
|
1798
|
+
if not model or not model.strip():
|
|
1799
|
+
raise ValueError("model must be a non-empty string")
|
|
1772
1800
|
with self._state:
|
|
1773
1801
|
# With a live session, perform the protocol switch first; if it
|
|
1774
1802
|
# fails we leave the persisted state untouched. Without one (pre
|
{openhands_sdk-1.55.0 → openhands_sdk-1.56.0}/openhands/sdk/conversation/impl/remote_conversation.py
RENAMED
|
@@ -13,6 +13,7 @@ from urllib.parse import urlparse
|
|
|
13
13
|
|
|
14
14
|
import httpx
|
|
15
15
|
import websockets
|
|
16
|
+
from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices
|
|
16
17
|
from websockets.exceptions import ConnectionClosed
|
|
17
18
|
|
|
18
19
|
from openhands.sdk.agent.base import AgentBase
|
|
@@ -34,6 +35,7 @@ from openhands.sdk.conversation.title_utils import generate_conversation_title
|
|
|
34
35
|
from openhands.sdk.conversation.types import (
|
|
35
36
|
ConversationCallbackType,
|
|
36
37
|
ConversationID,
|
|
38
|
+
ConversationTokenCallbackType,
|
|
37
39
|
StuckDetectionThresholds,
|
|
38
40
|
TraceMetadataValue,
|
|
39
41
|
)
|
|
@@ -49,6 +51,7 @@ from openhands.sdk.event.conversation_state import (
|
|
|
49
51
|
ConversationStateUpdateEvent,
|
|
50
52
|
)
|
|
51
53
|
from openhands.sdk.event.llm_completion_log import LLMCompletionLogEvent
|
|
54
|
+
from openhands.sdk.event.streaming_delta import StreamingDeltaEvent
|
|
52
55
|
from openhands.sdk.event.types import EventID
|
|
53
56
|
from openhands.sdk.hooks import HookConfig
|
|
54
57
|
from openhands.sdk.llm import LLM, Message, TextContent
|
|
@@ -92,6 +95,26 @@ def _validate_remote_agent(agent_data: dict) -> AgentBase:
|
|
|
92
95
|
return AgentBase.model_validate(agent_data)
|
|
93
96
|
|
|
94
97
|
|
|
98
|
+
def _token_delta_callback(
|
|
99
|
+
token_callbacks: list[ConversationTokenCallbackType],
|
|
100
|
+
) -> ConversationCallbackType:
|
|
101
|
+
"""Hand each streamed delta from the events socket to the token callbacks."""
|
|
102
|
+
on_token = BaseConversation.compose_callbacks(token_callbacks)
|
|
103
|
+
|
|
104
|
+
def callback(event: Event) -> None:
|
|
105
|
+
if isinstance(event, StreamingDeltaEvent):
|
|
106
|
+
delta = Delta(
|
|
107
|
+
content=event.content, reasoning_content=event.reasoning_content
|
|
108
|
+
)
|
|
109
|
+
on_token(
|
|
110
|
+
ModelResponseStream(
|
|
111
|
+
id=event.id, choices=[StreamingChoices(index=0, delta=delta)]
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return callback
|
|
116
|
+
|
|
117
|
+
|
|
95
118
|
def _restore_tool_registrations(tool_module_qualnames: Mapping[str, str]) -> None:
|
|
96
119
|
"""Import the tool modules persisted with a remote conversation."""
|
|
97
120
|
for tool_name, module_qualname in tool_module_qualnames.items():
|
|
@@ -493,9 +516,16 @@ class RemoteEventsList(EventsListBase):
|
|
|
493
516
|
return len(self._cached_events) - 1
|
|
494
517
|
|
|
495
518
|
def create_default_callback(self) -> ConversationCallbackType:
|
|
496
|
-
"""Create a default callback that adds events to this list.
|
|
519
|
+
"""Create a default callback that adds events to this list.
|
|
520
|
+
|
|
521
|
+
Streamed ``StreamingDeltaEvent`` frames are transient: the server never
|
|
522
|
+
stores them, so they reach the other callbacks but stay out of this
|
|
523
|
+
list, which keeps matching the server's event log.
|
|
524
|
+
"""
|
|
497
525
|
|
|
498
526
|
def callback(event: Event) -> None:
|
|
527
|
+
if isinstance(event, StreamingDeltaEvent):
|
|
528
|
+
return
|
|
499
529
|
self.add_event(event)
|
|
500
530
|
|
|
501
531
|
return callback
|
|
@@ -746,6 +776,7 @@ class RemoteConversation(BaseConversation):
|
|
|
746
776
|
observability_tags: list[str] | None = None,
|
|
747
777
|
observability_span_name: str = "conversation",
|
|
748
778
|
observability_parent_span_context: str | None = None,
|
|
779
|
+
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
749
780
|
**_: object,
|
|
750
781
|
) -> None:
|
|
751
782
|
"""Remote conversation proxy that talks to an agent server.
|
|
@@ -783,6 +814,8 @@ class RemoteConversation(BaseConversation):
|
|
|
783
814
|
observability_tags: Optional root span tags for observability backends.
|
|
784
815
|
observability_span_name: Optional child span name for observability
|
|
785
816
|
backends. The root span remains named "conversation".
|
|
817
|
+
token_callbacks: Optional callbacks invoked with each streamed token
|
|
818
|
+
delta the server sends (agent LLM with ``stream=True``).
|
|
786
819
|
"""
|
|
787
820
|
observability_metadata = merge_observability_metadata(observability_metadata)
|
|
788
821
|
observability_parent_span_context = (
|
|
@@ -920,6 +953,7 @@ class RemoteConversation(BaseConversation):
|
|
|
920
953
|
workspace=workspace,
|
|
921
954
|
conversation_id=conversation_id,
|
|
922
955
|
callbacks=callbacks,
|
|
956
|
+
token_callbacks=token_callbacks,
|
|
923
957
|
max_iteration_per_run=max_iteration_per_run,
|
|
924
958
|
client_tools=[*(client_tools or []), *attached_client_tools],
|
|
925
959
|
visualizer=visualizer,
|
|
@@ -1060,10 +1094,13 @@ class RemoteConversation(BaseConversation):
|
|
|
1060
1094
|
visualizer: (
|
|
1061
1095
|
type[ConversationVisualizerBase] | ConversationVisualizerBase | None
|
|
1062
1096
|
),
|
|
1097
|
+
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
1063
1098
|
) -> None:
|
|
1064
1099
|
super().__init__() # Initialize base class with span tracking
|
|
1065
1100
|
self.agent = agent
|
|
1066
1101
|
self._callbacks = callbacks or []
|
|
1102
|
+
if token_callbacks:
|
|
1103
|
+
self._callbacks.append(_token_delta_callback(token_callbacks))
|
|
1067
1104
|
self.max_iteration_per_run = max_iteration_per_run
|
|
1068
1105
|
self.workspace = workspace
|
|
1069
1106
|
self._client = workspace.client
|
|
@@ -1166,13 +1203,40 @@ class RemoteConversation(BaseConversation):
|
|
|
1166
1203
|
all_callbacks = self._callbacks + [run_complete_callback]
|
|
1167
1204
|
composed_callback = BaseConversation.compose_callbacks(all_callbacks)
|
|
1168
1205
|
|
|
1206
|
+
# Track which events the callbacks have seen, so that events the
|
|
1207
|
+
# server recorded while the socket was reconnecting (and that only
|
|
1208
|
+
# reached the cache through REST reconciliation) can be replayed to
|
|
1209
|
+
# them once, without the live socket delivering them a second time.
|
|
1210
|
+
delivered_ids: set[str] = set()
|
|
1211
|
+
replayed_ids: set[str] = set()
|
|
1212
|
+
|
|
1213
|
+
def socket_callback(event: Event) -> None:
|
|
1214
|
+
if event.id in replayed_ids:
|
|
1215
|
+
replayed_ids.discard(event.id)
|
|
1216
|
+
return
|
|
1217
|
+
delivered_ids.add(event.id)
|
|
1218
|
+
composed_callback(event)
|
|
1219
|
+
|
|
1220
|
+
def replay_missed_events() -> None:
|
|
1221
|
+
self._state.events.reconcile()
|
|
1222
|
+
replayed_ids.clear()
|
|
1223
|
+
for event in self._state.events[:]:
|
|
1224
|
+
if event.id in delivered_ids:
|
|
1225
|
+
continue
|
|
1226
|
+
delivered_ids.add(event.id)
|
|
1227
|
+
replayed_ids.add(event.id)
|
|
1228
|
+
try:
|
|
1229
|
+
composed_callback(event)
|
|
1230
|
+
except Exception:
|
|
1231
|
+
logger.exception("ws_replay_callback_error", stack_info=True)
|
|
1232
|
+
|
|
1169
1233
|
# Initialize WebSocket client for callbacks
|
|
1170
1234
|
self._ws_client = WebSocketCallbackClient(
|
|
1171
1235
|
host=self.workspace.host,
|
|
1172
1236
|
conversation_id=str(self._id),
|
|
1173
|
-
callback=
|
|
1237
|
+
callback=socket_callback,
|
|
1174
1238
|
api_key=self.workspace.api_key,
|
|
1175
|
-
on_reconnect=
|
|
1239
|
+
on_reconnect=replay_missed_events,
|
|
1176
1240
|
)
|
|
1177
1241
|
self._ws_client.start()
|
|
1178
1242
|
|
|
@@ -1210,6 +1274,9 @@ class RemoteConversation(BaseConversation):
|
|
|
1210
1274
|
# were emitted between the initial REST sync and WebSocket subscription.
|
|
1211
1275
|
# This is the "reconciliation" part of the subscription handshake.
|
|
1212
1276
|
self._state.events.reconcile()
|
|
1277
|
+
# Callbacks are not replayed history: only events recorded after this
|
|
1278
|
+
# point and missed by the socket are replayed on reconnect.
|
|
1279
|
+
delivered_ids.update(event.id for event in self._state.events[:])
|
|
1213
1280
|
|
|
1214
1281
|
def _create_llm_completion_log_callback(self) -> ConversationCallbackType:
|
|
1215
1282
|
"""Create a callback that writes LLM completion logs to client filesystem."""
|
|
@@ -8,7 +8,7 @@ agent-server.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
from typing import Annotated, Any, cast
|
|
11
|
+
from typing import Annotated, Any, Literal, cast
|
|
12
12
|
from uuid import UUID
|
|
13
13
|
|
|
14
14
|
from pydantic import (
|
|
@@ -65,6 +65,53 @@ ACPEnabledAgent = Annotated[
|
|
|
65
65
|
# ---------------------------------------------------------------------------
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
class StartChildConversationRequest(BaseModel):
|
|
69
|
+
"""Payload to start a child conversation on the same backend as its parent.
|
|
70
|
+
|
|
71
|
+
Sent by the ``start_child_conversation`` tool to a child-launch endpoint:
|
|
72
|
+
the agent-server's ``POST /api/conversations/{id}/children`` by default, or
|
|
73
|
+
an equivalent launcher supplied by the backend hosting the parent.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
task: str = Field(
|
|
77
|
+
min_length=1,
|
|
78
|
+
description=(
|
|
79
|
+
"Self-contained brief delivered to the child as its first user message."
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
title: str | None = Field(
|
|
83
|
+
default=None, description="Optional title for the child conversation."
|
|
84
|
+
)
|
|
85
|
+
isolation: Literal["worktree", "shared"] = Field(
|
|
86
|
+
default="worktree",
|
|
87
|
+
description=(
|
|
88
|
+
"Workspace isolation for the child. 'worktree' runs it in a git "
|
|
89
|
+
"worktree carved from the parent's workspace when that workspace is a "
|
|
90
|
+
"git repository; 'shared' runs it in the parent's directory. Backends "
|
|
91
|
+
"that manage workspaces themselves may ignore this."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class StartChildConversationResponse(BaseModel):
|
|
97
|
+
"""Result of a child conversation launch, returned to the parent agent."""
|
|
98
|
+
|
|
99
|
+
conversation_id: UUID
|
|
100
|
+
parent_conversation_id: UUID
|
|
101
|
+
status: str = Field(description="Initial launch status reported by the backend.")
|
|
102
|
+
title: str | None = None
|
|
103
|
+
url: str | None = Field(
|
|
104
|
+
default=None,
|
|
105
|
+
description="Link to the child conversation, when the backend knows it.",
|
|
106
|
+
)
|
|
107
|
+
workspace: str | None = Field(
|
|
108
|
+
default=None, description="Working directory of the child, when known."
|
|
109
|
+
)
|
|
110
|
+
isolation: Literal["worktree", "shared"] | None = Field(
|
|
111
|
+
default=None, description="Workspace isolation actually applied, when known."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
68
115
|
class AgentLaunchAdditions(BaseModel):
|
|
69
116
|
"""Add deployment context after agent resolution."""
|
|
70
117
|
|
|
@@ -9,7 +9,11 @@ import logging
|
|
|
9
9
|
import os
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from openhands.sdk.git.exceptions import
|
|
12
|
+
from openhands.sdk.git.exceptions import (
|
|
13
|
+
GitCommandError,
|
|
14
|
+
GitError,
|
|
15
|
+
GitRepositoryError,
|
|
16
|
+
)
|
|
13
17
|
from openhands.sdk.git.models import GitChange, GitChangeStatus
|
|
14
18
|
from openhands.sdk.git.utils import (
|
|
15
19
|
get_valid_ref,
|
|
@@ -154,7 +158,9 @@ def get_changes_in_repo(
|
|
|
154
158
|
vs the latest commit) instead.
|
|
155
159
|
|
|
156
160
|
Args:
|
|
157
|
-
repo_dir: Path to the git repository
|
|
161
|
+
repo_dir: Path to the git repository, or to a directory inside one.
|
|
162
|
+
Only changes under this directory are listed, with paths relative
|
|
163
|
+
to it.
|
|
158
164
|
ref: Optional explicit ref to compare against (e.g. ``"HEAD"`` or a
|
|
159
165
|
commit hash). When ``None``, behaves as before and compares
|
|
160
166
|
against the upstream/default branch.
|
|
@@ -178,10 +184,14 @@ def get_changes_in_repo(
|
|
|
178
184
|
logger.warning(f"No valid git reference found for {validated_repo}")
|
|
179
185
|
return []
|
|
180
186
|
|
|
181
|
-
# Get changed files using secure git command
|
|
187
|
+
# Get changed files using secure git command. ``--relative`` limits the
|
|
188
|
+
# diff to ``repo_dir`` and prints paths relative to it, the same base as
|
|
189
|
+
# ``git ls-files --others`` below, so a subdirectory of a repository does
|
|
190
|
+
# not mix repository-relative and subdirectory-relative paths.
|
|
182
191
|
try:
|
|
183
192
|
changed_files_output = run_git_command(
|
|
184
|
-
["git", "--no-pager", "diff", "--name-status", ref],
|
|
193
|
+
["git", "--no-pager", "diff", "--name-status", "--relative", ref],
|
|
194
|
+
validated_repo,
|
|
185
195
|
)
|
|
186
196
|
changed_files = (
|
|
187
197
|
changed_files_output.splitlines() if changed_files_output else []
|
|
@@ -221,8 +231,17 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
221
231
|
for f in glob.glob("./*/.git", root_dir=cwd, recursive=True)
|
|
222
232
|
}
|
|
223
233
|
|
|
224
|
-
# First try the workspace directory
|
|
225
|
-
|
|
234
|
+
# First try the workspace directory. A directory that is not a repository
|
|
235
|
+
# itself but holds repositories below it (the ``{base}/{repo}`` layout of a
|
|
236
|
+
# repository-backed conversation) contributes no changes of its own.
|
|
237
|
+
base_error: GitRepositoryError | None = None
|
|
238
|
+
try:
|
|
239
|
+
changes = get_changes_in_repo(cwd, ref=ref)
|
|
240
|
+
except GitRepositoryError as e:
|
|
241
|
+
if not git_dirs:
|
|
242
|
+
raise
|
|
243
|
+
base_error = e
|
|
244
|
+
changes = []
|
|
226
245
|
|
|
227
246
|
# Filter out any changes which are inside one of the nested repositories.
|
|
228
247
|
# This compares path ancestry rather than string prefixes: a nested
|
|
@@ -236,6 +255,7 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
236
255
|
]
|
|
237
256
|
|
|
238
257
|
# Add changes from git directories
|
|
258
|
+
found_nested_repo = False
|
|
239
259
|
for git_dir in git_dirs:
|
|
240
260
|
try:
|
|
241
261
|
git_dir_changes = get_changes_in_repo(str(Path(cwd, git_dir)), ref=ref)
|
|
@@ -244,6 +264,7 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
244
264
|
f"Skipping nested git directory {git_dir}: not a valid repository"
|
|
245
265
|
)
|
|
246
266
|
continue
|
|
267
|
+
found_nested_repo = True
|
|
247
268
|
for change in git_dir_changes:
|
|
248
269
|
# Create a new GitChange with the updated path
|
|
249
270
|
updated_change = GitChange(
|
|
@@ -252,6 +273,10 @@ def get_git_changes(cwd: str | Path, ref: str | None = None) -> list[GitChange]:
|
|
|
252
273
|
)
|
|
253
274
|
changes.append(updated_change)
|
|
254
275
|
|
|
276
|
+
# Neither the directory nor anything below it is a repository.
|
|
277
|
+
if base_error is not None and not found_nested_repo:
|
|
278
|
+
raise base_error
|
|
279
|
+
|
|
255
280
|
changes.sort(key=lambda change: str(change.path))
|
|
256
281
|
|
|
257
282
|
return changes
|
|
@@ -34,6 +34,12 @@ _LOCK_TIMEOUT_SECONDS: Final[float] = 30.0
|
|
|
34
34
|
PROFILE_NAME_PATTERN: Final[str] = r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
|
|
35
35
|
PROFILE_NAME_REGEX: Final[re.Pattern[str]] = re.compile(PROFILE_NAME_PATTERN)
|
|
36
36
|
|
|
37
|
+
|
|
38
|
+
def canonical_profile_name(name: str) -> str:
|
|
39
|
+
"""Return the store key ``name`` addresses: ``foo.json`` is profile ``foo``."""
|
|
40
|
+
return name.removesuffix(".json")
|
|
41
|
+
|
|
42
|
+
|
|
37
43
|
logger = get_logger(__name__)
|
|
38
44
|
|
|
39
45
|
|
|
@@ -163,7 +169,7 @@ class LLMProfileStore:
|
|
|
163
169
|
Raises:
|
|
164
170
|
ValueError: If name does not match the allowed pattern.
|
|
165
171
|
"""
|
|
166
|
-
clean_name = name
|
|
172
|
+
clean_name = canonical_profile_name(name)
|
|
167
173
|
if not PROFILE_NAME_REGEX.match(clean_name):
|
|
168
174
|
raise ValueError(
|
|
169
175
|
f"Invalid profile name: {name!r}. "
|
|
@@ -10,7 +10,6 @@ string — credentials/provider settings resolve through that store.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
import os
|
|
14
13
|
import re
|
|
15
14
|
import tempfile
|
|
16
15
|
from collections.abc import Iterator
|
|
@@ -23,6 +22,7 @@ from pydantic import BaseModel, Field, model_validator
|
|
|
23
22
|
|
|
24
23
|
from openhands.sdk.llm.llm_profile_store import PROFILE_NAME_REGEX
|
|
25
24
|
from openhands.sdk.logger import get_logger
|
|
25
|
+
from openhands.sdk.utils.path import get_user_persistence_dir
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
_LOCK_TIMEOUT_SECONDS: Final[float] = 30.0
|
|
@@ -37,11 +37,10 @@ def default_meta_profile_dir() -> Path:
|
|
|
37
37
|
persisted state. Honor that for bare ``MetaProfileStore()`` instances so
|
|
38
38
|
runtime tools read the same meta-profiles exposed by the HTTP API. Without
|
|
39
39
|
that env var, keep the standalone SDK default under ``~/.openhands``.
|
|
40
|
+
Resolved at call time through ``get_user_persistence_dir`` so ``~`` and
|
|
41
|
+
relative values land where the other stores put their files.
|
|
40
42
|
"""
|
|
41
|
-
|
|
42
|
-
if env_dir:
|
|
43
|
-
return Path(env_dir) / "meta-profiles"
|
|
44
|
-
return Path.home() / ".openhands" / "meta-profiles"
|
|
43
|
+
return get_user_persistence_dir() / "meta-profiles"
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
class MetaProfileLimitExceeded(Exception):
|
|
@@ -51,6 +51,19 @@ def _list_bedrock_foundation_models(
|
|
|
51
51
|
return []
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def _is_litellm_bedrock_model(model: str) -> bool:
|
|
55
|
+
"""Whether LiteLLM serves ``model`` only through AWS Bedrock.
|
|
56
|
+
|
|
57
|
+
Many Bedrock ids carry no ``bedrock`` prefix (``deepseek.r1-v1:0``,
|
|
58
|
+
``us.anthropic.claude-...``), so ask LiteLLM's model-cost map which provider
|
|
59
|
+
it files the id under (``bedrock``, ``bedrock_converse``, ``bedrock_mantle``).
|
|
60
|
+
"""
|
|
61
|
+
if model.startswith("bedrock"):
|
|
62
|
+
return True
|
|
63
|
+
provider = litellm.model_cost.get(model, {}).get("litellm_provider")
|
|
64
|
+
return isinstance(provider, str) and provider.startswith("bedrock")
|
|
65
|
+
|
|
66
|
+
|
|
54
67
|
def get_supported_llm_models(
|
|
55
68
|
aws_region_name: str | None = None,
|
|
56
69
|
aws_access_key_id: SecretStr | None = None,
|
|
@@ -68,9 +81,9 @@ def get_supported_llm_models(
|
|
|
68
81
|
+ list(litellm.model_cost.keys())
|
|
69
82
|
+ [f"openrouter/{model}" for model in VERIFIED_MODELS["openrouter"]]
|
|
70
83
|
)
|
|
71
|
-
litellm_model_list_without_bedrock =
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
litellm_model_list_without_bedrock = [
|
|
85
|
+
m for m in litellm_model_list if not _is_litellm_bedrock_model(m)
|
|
86
|
+
]
|
|
74
87
|
bedrock_model_list = []
|
|
75
88
|
if aws_region_name and aws_access_key_id and aws_secret_access_key:
|
|
76
89
|
bedrock_model_list = _list_bedrock_foundation_models(
|
|
@@ -76,6 +76,25 @@ class MarketplaceNotFoundError(PluginResolutionError):
|
|
|
76
76
|
super().__init__(f"Marketplace '{marketplace_name}' is not registered")
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
def _load_marketplace(path: Path, name: str) -> Marketplace:
|
|
80
|
+
"""Load the manifest, or read a source without one as a plain skills folder."""
|
|
81
|
+
try:
|
|
82
|
+
return Marketplace.load(path)
|
|
83
|
+
except FileNotFoundError as missing_manifest:
|
|
84
|
+
marketplace = Marketplace.from_skills_dir(path, name)
|
|
85
|
+
if not marketplace.skills:
|
|
86
|
+
raise FileNotFoundError(
|
|
87
|
+
f"{missing_manifest}, and no skill folders (<name>/SKILL.md) "
|
|
88
|
+
"were found there"
|
|
89
|
+
) from missing_manifest
|
|
90
|
+
logger.info(
|
|
91
|
+
"Marketplace '%s' has no manifest; using %d skills from its folder",
|
|
92
|
+
name,
|
|
93
|
+
len(marketplace.skills),
|
|
94
|
+
)
|
|
95
|
+
return marketplace
|
|
96
|
+
|
|
97
|
+
|
|
79
98
|
class MarketplaceRegistry:
|
|
80
99
|
"""Resolve plugin references against registered marketplaces."""
|
|
81
100
|
|
|
@@ -165,7 +184,7 @@ class MarketplaceRegistry:
|
|
|
165
184
|
repo_path=registration.repo_path,
|
|
166
185
|
)
|
|
167
186
|
fetched = FetchedMarketplace(
|
|
168
|
-
marketplace=
|
|
187
|
+
marketplace=_load_marketplace(path, registration.name),
|
|
169
188
|
path=path,
|
|
170
189
|
resolved_ref=resolved_ref,
|
|
171
190
|
)
|
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
10
10
|
|
|
11
|
+
from openhands.sdk.logger import get_logger
|
|
11
12
|
from openhands.sdk.plugin.types import (
|
|
12
13
|
HooksConfigDict,
|
|
13
14
|
LspServersDict,
|
|
@@ -17,6 +18,8 @@ from openhands.sdk.plugin.types import (
|
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
20
23
|
# Directories to check for marketplace manifest
|
|
21
24
|
MARKETPLACE_MANIFEST_DIRS = [".plugin", ".claude-plugin"]
|
|
22
25
|
MARKETPLACE_MANIFEST_FILE = "marketplace.json"
|
|
@@ -260,6 +263,58 @@ class Marketplace(BaseModel):
|
|
|
260
263
|
|
|
261
264
|
return cls.model_validate({**data, "path": str(marketplace_dir)})
|
|
262
265
|
|
|
266
|
+
@classmethod
|
|
267
|
+
def from_skills_dir(cls, marketplace_path: str | Path, name: str) -> Marketplace:
|
|
268
|
+
"""Build a marketplace from a plain folder of skills, without a manifest.
|
|
269
|
+
|
|
270
|
+
Each ``<name>/SKILL.md`` folder becomes a standalone skill: those in
|
|
271
|
+
``skills/`` and ``.skills/``, or, when neither exists, those directly in
|
|
272
|
+
``marketplace_path``, so a ``repo_path`` can point at the skills folder
|
|
273
|
+
itself. Loose markdown files are not read, so a repository's README and
|
|
274
|
+
docs never become skills. The result has no skills when none are found.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
marketplace_path: Path to the repository or skills folder.
|
|
278
|
+
name: Marketplace name, since there is no manifest to supply one.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Marketplace listing the discovered skills, with no plugins.
|
|
282
|
+
"""
|
|
283
|
+
from openhands.sdk.skills import Skill
|
|
284
|
+
from openhands.sdk.skills.utils import find_skill_md_directories
|
|
285
|
+
|
|
286
|
+
marketplace_dir = Path(marketplace_path).resolve()
|
|
287
|
+
skills_dirs = [
|
|
288
|
+
path
|
|
289
|
+
for path in (marketplace_dir / "skills", marketplace_dir / ".skills")
|
|
290
|
+
if path.is_dir()
|
|
291
|
+
] or [marketplace_dir]
|
|
292
|
+
|
|
293
|
+
entries: dict[str, MarketplaceEntry] = {}
|
|
294
|
+
for skills_dir in skills_dirs:
|
|
295
|
+
for skill_md in find_skill_md_directories(skills_dir, marketplace_dir):
|
|
296
|
+
try:
|
|
297
|
+
skill = Skill.load(skill_md, strict=False)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.warning(f"Failed to load skill from {skill_md}: {e}")
|
|
300
|
+
continue
|
|
301
|
+
source = skill_md.parent.relative_to(marketplace_dir).as_posix()
|
|
302
|
+
entries.setdefault(
|
|
303
|
+
skill.name,
|
|
304
|
+
MarketplaceEntry(
|
|
305
|
+
name=skill.name,
|
|
306
|
+
source=f"./{source}",
|
|
307
|
+
description=skill.description,
|
|
308
|
+
),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
return cls(
|
|
312
|
+
name=name,
|
|
313
|
+
owner=MarketplaceOwner(name=name),
|
|
314
|
+
skills=list(entries.values()),
|
|
315
|
+
path=str(marketplace_dir),
|
|
316
|
+
)
|
|
317
|
+
|
|
263
318
|
def get_plugin(self, name: str) -> MarketplacePluginEntry | None:
|
|
264
319
|
"""Get a plugin entry by name.
|
|
265
320
|
|