openhands-sdk 1.3.0__tar.gz → 1.4.1__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.3.0 → openhands_sdk-1.4.1}/PKG-INFO +2 -2
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/__init__.py +4 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/agent.py +55 -22
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/base.py +8 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/system_prompt.j2 +1 -11
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/utils.py +5 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/agent_context.py +30 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/skills/__init__.py +2 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/skills/skill.py +202 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/__init__.py +5 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/base.py +15 -6
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/conversation.py +10 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/conversation_stats.py +38 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/fifo_lock.py +14 -8
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/impl/local_conversation.py +21 -5
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/secret_source.py +1 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/state.py +8 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/types.py +5 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/conversation_state.py +8 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/__init__.py +3 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/llm.py +82 -16
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/llm_registry.py +1 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/options/chat_options.py +12 -24
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/options/responses_options.py +9 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/router/base.py +3 -0
- openhands_sdk-1.4.1/openhands/sdk/llm/streaming.py +9 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/utils/model_features.py +12 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/logger/logger.py +7 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/tool.py +18 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/models.py +90 -9
- openhands_sdk-1.4.1/openhands/sdk/utils/truncate.py +117 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/__init__.py +3 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/models.py +7 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/remote/async_remote_workspace.py +22 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/remote/base.py +13 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands_sdk.egg-info/PKG-INFO +2 -2
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands_sdk.egg-info/SOURCES.txt +2 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands_sdk.egg-info/requires.txt +1 -1
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/pyproject.toml +2 -2
- openhands_sdk-1.3.0/openhands/sdk/utils/truncate.py +0 -44
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/in_context_learning_example.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/security_policy.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/security_risk_assessment.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/system_prompt_interactive.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/system_prompt_planning.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/condenser/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/condenser/base.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/prompts/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/prompts/prompt.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/prompts/templates/system_message_suffix.j2 +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/skills/exceptions.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/skills/trigger.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/skills/types.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/context/view.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/event_store.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/events_list_base.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/exceptions.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/impl/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/impl/remote_conversation.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/persistence_const.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/response_utils.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/secret_registry.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/serialization_diff.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/stuck_detector.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/title_utils.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/visualizer/base.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/conversation/visualizer/default.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/critic/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/critic/base.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/critic/impl/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/critic/impl/agent_finished.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/critic/impl/empty_patch.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/critic/impl/pass_critic.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/base.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/condenser.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/conversation_error.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/llm_completion_log.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/llm_convertible/action.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/llm_convertible/message.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/llm_convertible/observation.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/llm_convertible/system.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/token.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/types.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/event/user_action.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/git/exceptions.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/git/git_changes.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/git/git_diff.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/git/models.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/git/utils.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/io/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/io/base.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/io/local.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/io/memory.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/exceptions/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/exceptions/classifier.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/exceptions/mapping.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/exceptions/types.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/llm_response.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/message.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/options/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/options/common.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/router/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/router/impl/random.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/utils/metrics.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/utils/model_info.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/utils/retry_mixin.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/utils/telemetry.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/utils/unverified_models.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/llm/utils/verified_models.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/logger/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/logger/rolling.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/mcp/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/mcp/client.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/mcp/definition.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/mcp/exceptions.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/mcp/tool.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/mcp/utils.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/observability/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/observability/laminar.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/observability/utils.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/py.typed +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/security/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/security/analyzer.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/security/confirmation_policy.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/security/llm_analyzer.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/security/risk.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/builtins/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/builtins/finish.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/builtins/think.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/registry.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/schema.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/tool/spec.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/async_executor.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/async_utils.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/cipher.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/command.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/deprecation.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/github.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/json.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/pydantic_diff.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/pydantic_secrets.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/utils/visualize.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/base.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/local.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/remote/__init__.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands/sdk/workspace/workspace.py +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands_sdk.egg-info/dependency_links.txt +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/openhands_sdk.egg-info/top_level.txt +0 -0
- {openhands_sdk-1.3.0 → openhands_sdk-1.4.1}/setup.cfg +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: OpenHands SDK - Core functionality for building AI agents
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: deprecation>=2.1.0
|
|
7
7
|
Requires-Dist: fastmcp>=2.11.3
|
|
8
8
|
Requires-Dist: httpx>=0.27.0
|
|
9
|
-
Requires-Dist: litellm>=1.
|
|
9
|
+
Requires-Dist: litellm>=1.80.7
|
|
10
10
|
Requires-Dist: pydantic>=2.11.7
|
|
11
11
|
Requires-Dist: python-frontmatter>=1.1.0
|
|
12
12
|
Requires-Dist: python-json-logger>=3.3.0
|
|
@@ -21,11 +21,13 @@ from openhands.sdk.llm import (
|
|
|
21
21
|
LLM,
|
|
22
22
|
ImageContent,
|
|
23
23
|
LLMRegistry,
|
|
24
|
+
LLMStreamChunk,
|
|
24
25
|
Message,
|
|
25
26
|
RedactedThinkingBlock,
|
|
26
27
|
RegistryEvent,
|
|
27
28
|
TextContent,
|
|
28
29
|
ThinkingBlock,
|
|
30
|
+
TokenCallbackType,
|
|
29
31
|
)
|
|
30
32
|
from openhands.sdk.logger import get_logger
|
|
31
33
|
from openhands.sdk.mcp import (
|
|
@@ -58,6 +60,8 @@ except PackageNotFoundError:
|
|
|
58
60
|
__all__ = [
|
|
59
61
|
"LLM",
|
|
60
62
|
"LLMRegistry",
|
|
63
|
+
"LLMStreamChunk",
|
|
64
|
+
"TokenCallbackType",
|
|
61
65
|
"ConversationStats",
|
|
62
66
|
"RegistryEvent",
|
|
63
67
|
"Message",
|
|
@@ -13,6 +13,7 @@ from openhands.sdk.agent.utils import (
|
|
|
13
13
|
from openhands.sdk.conversation import (
|
|
14
14
|
ConversationCallbackType,
|
|
15
15
|
ConversationState,
|
|
16
|
+
ConversationTokenCallbackType,
|
|
16
17
|
LocalConversation,
|
|
17
18
|
)
|
|
18
19
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
@@ -27,6 +28,7 @@ from openhands.sdk.event import (
|
|
|
27
28
|
)
|
|
28
29
|
from openhands.sdk.event.condenser import Condensation, CondensationRequest
|
|
29
30
|
from openhands.sdk.llm import (
|
|
31
|
+
LLMResponse,
|
|
30
32
|
Message,
|
|
31
33
|
MessageToolCall,
|
|
32
34
|
ReasoningItemModel,
|
|
@@ -135,6 +137,7 @@ class Agent(AgentBase):
|
|
|
135
137
|
self,
|
|
136
138
|
conversation: LocalConversation,
|
|
137
139
|
on_event: ConversationCallbackType,
|
|
140
|
+
on_token: ConversationTokenCallbackType | None = None,
|
|
138
141
|
) -> None:
|
|
139
142
|
state = conversation.state
|
|
140
143
|
# Check for pending actions (implicit confirmation)
|
|
@@ -167,7 +170,10 @@ class Agent(AgentBase):
|
|
|
167
170
|
|
|
168
171
|
try:
|
|
169
172
|
llm_response = make_llm_completion(
|
|
170
|
-
self.llm,
|
|
173
|
+
self.llm,
|
|
174
|
+
_messages,
|
|
175
|
+
tools=list(self.tools_map.values()),
|
|
176
|
+
on_token=on_token,
|
|
171
177
|
)
|
|
172
178
|
except FunctionCallValidationError as e:
|
|
173
179
|
logger.warning(f"LLM generated malformed function call: {e}")
|
|
@@ -197,6 +203,17 @@ class Agent(AgentBase):
|
|
|
197
203
|
# LLMResponse already contains the converted message and metrics snapshot
|
|
198
204
|
message: Message = llm_response.message
|
|
199
205
|
|
|
206
|
+
# Check if this is a reasoning-only response (e.g., from reasoning models)
|
|
207
|
+
# or a message-only response without tool calls
|
|
208
|
+
has_reasoning = (
|
|
209
|
+
message.responses_reasoning_item is not None
|
|
210
|
+
or message.reasoning_content is not None
|
|
211
|
+
or (message.thinking_blocks and len(message.thinking_blocks) > 0)
|
|
212
|
+
)
|
|
213
|
+
has_content = any(
|
|
214
|
+
isinstance(c, TextContent) and c.text.strip() for c in message.content
|
|
215
|
+
)
|
|
216
|
+
|
|
200
217
|
if message.tool_calls and len(message.tool_calls) > 0:
|
|
201
218
|
if not all(isinstance(c, TextContent) for c in message.content):
|
|
202
219
|
logger.warning(
|
|
@@ -236,29 +253,30 @@ class Agent(AgentBase):
|
|
|
236
253
|
if action_events:
|
|
237
254
|
self._execute_actions(conversation, action_events, on_event)
|
|
238
255
|
|
|
239
|
-
|
|
256
|
+
# Emit VLLM token ids if enabled before returning
|
|
257
|
+
self._maybe_emit_vllm_tokens(llm_response, on_event)
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
# No tool calls - emit message event for reasoning or content responses
|
|
261
|
+
if not has_reasoning and not has_content:
|
|
262
|
+
logger.warning("LLM produced empty response - continuing agent loop")
|
|
263
|
+
|
|
264
|
+
msg_event = MessageEvent(
|
|
265
|
+
source="agent",
|
|
266
|
+
llm_message=message,
|
|
267
|
+
llm_response_id=llm_response.id,
|
|
268
|
+
)
|
|
269
|
+
on_event(msg_event)
|
|
270
|
+
|
|
271
|
+
# Emit VLLM token ids if enabled
|
|
272
|
+
self._maybe_emit_vllm_tokens(llm_response, on_event)
|
|
273
|
+
|
|
274
|
+
# Finish conversation if LLM produced content (awaits user input)
|
|
275
|
+
# Continue if only reasoning without content (e.g., GPT-5 codex thinking)
|
|
276
|
+
if has_content:
|
|
240
277
|
logger.debug("LLM produced a message response - awaits user input")
|
|
241
278
|
state.execution_status = ConversationExecutionStatus.FINISHED
|
|
242
|
-
|
|
243
|
-
source="agent",
|
|
244
|
-
llm_message=message,
|
|
245
|
-
llm_response_id=llm_response.id,
|
|
246
|
-
)
|
|
247
|
-
on_event(msg_event)
|
|
248
|
-
|
|
249
|
-
# If using VLLM, we can get the raw prompt and response tokens
|
|
250
|
-
# that can be useful for RL training.
|
|
251
|
-
if (
|
|
252
|
-
"return_token_ids" in self.llm.litellm_extra_body
|
|
253
|
-
) and self.llm.litellm_extra_body["return_token_ids"]:
|
|
254
|
-
token_event = TokenEvent(
|
|
255
|
-
source="agent",
|
|
256
|
-
prompt_token_ids=llm_response.raw_response["prompt_token_ids"],
|
|
257
|
-
response_token_ids=llm_response.raw_response["choices"][0][
|
|
258
|
-
"provider_specific_fields"
|
|
259
|
-
]["token_ids"],
|
|
260
|
-
)
|
|
261
|
-
on_event(token_event)
|
|
279
|
+
return
|
|
262
280
|
|
|
263
281
|
def _requires_user_confirmation(
|
|
264
282
|
self, state: ConversationState, action_events: list[ActionEvent]
|
|
@@ -483,3 +501,18 @@ class Agent(AgentBase):
|
|
|
483
501
|
if tool.name == FinishTool.name:
|
|
484
502
|
state.execution_status = ConversationExecutionStatus.FINISHED
|
|
485
503
|
return obs_event
|
|
504
|
+
|
|
505
|
+
def _maybe_emit_vllm_tokens(
|
|
506
|
+
self, llm_response: LLMResponse, on_event: ConversationCallbackType
|
|
507
|
+
) -> None:
|
|
508
|
+
if (
|
|
509
|
+
"return_token_ids" in self.llm.litellm_extra_body
|
|
510
|
+
) and self.llm.litellm_extra_body["return_token_ids"]:
|
|
511
|
+
token_event = TokenEvent(
|
|
512
|
+
source="agent",
|
|
513
|
+
prompt_token_ids=llm_response.raw_response["prompt_token_ids"],
|
|
514
|
+
response_token_ids=llm_response.raw_response["choices"][0][
|
|
515
|
+
"provider_specific_fields"
|
|
516
|
+
]["token_ids"],
|
|
517
|
+
)
|
|
518
|
+
on_event(token_event)
|
|
@@ -20,7 +20,10 @@ from openhands.sdk.utils.pydantic_diff import pretty_pydantic_diff
|
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from openhands.sdk.conversation import ConversationState, LocalConversation
|
|
23
|
-
from openhands.sdk.conversation.types import
|
|
23
|
+
from openhands.sdk.conversation.types import (
|
|
24
|
+
ConversationCallbackType,
|
|
25
|
+
ConversationTokenCallbackType,
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
logger = get_logger(__name__)
|
|
@@ -239,6 +242,7 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
239
242
|
self,
|
|
240
243
|
conversation: "LocalConversation",
|
|
241
244
|
on_event: "ConversationCallbackType",
|
|
245
|
+
on_token: "ConversationTokenCallbackType | None" = None,
|
|
242
246
|
) -> None:
|
|
243
247
|
"""Taking a step in the conversation.
|
|
244
248
|
|
|
@@ -250,6 +254,9 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
|
|
|
250
254
|
4.1 If conversation is finished, set state.execution_status to FINISHED
|
|
251
255
|
4.2 Otherwise, just return, Conversation will kick off the next step
|
|
252
256
|
|
|
257
|
+
If the underlying LLM supports streaming, partial deltas are forwarded to
|
|
258
|
+
``on_token`` before the full response is returned.
|
|
259
|
+
|
|
253
260
|
NOTE: state will be mutated in-place.
|
|
254
261
|
"""
|
|
255
262
|
|
|
@@ -90,20 +90,10 @@ You are OpenHands agent, a helpful AI assistant that can interact with a compute
|
|
|
90
90
|
1. Step back and reflect on 5-7 different possible sources of the problem
|
|
91
91
|
2. Assess the likelihood of each possible cause
|
|
92
92
|
3. Methodically address the most likely causes, starting with the highest probability
|
|
93
|
-
4.
|
|
93
|
+
4. Explain your reasoning process in your response to the user
|
|
94
94
|
* When you run into any major issue while executing a plan from the user, please don't try to directly work around it. Instead, propose a new plan and confirm with the user before proceeding.
|
|
95
95
|
</TROUBLESHOOTING>
|
|
96
96
|
|
|
97
|
-
<DOCUMENTATION>
|
|
98
|
-
* When explaining changes or solutions to the user:
|
|
99
|
-
- Include explanations in your conversation responses rather than creating separate documentation files
|
|
100
|
-
- If you need to create documentation files for reference, do NOT include them in version control unless explicitly requested
|
|
101
|
-
- Never create multiple versions of documentation files with different suffixes
|
|
102
|
-
* If the user asks for documentation:
|
|
103
|
-
- Confirm whether they want it as a separate file or just in the conversation
|
|
104
|
-
- Ask if they want documentation files to be included in version control
|
|
105
|
-
</DOCUMENTATION>
|
|
106
|
-
|
|
107
97
|
<PROCESS_MANAGEMENT>
|
|
108
98
|
* When terminating processes:
|
|
109
99
|
- Do NOT use general keywords with commands like `pkill -f server` or `pkill -f python` as this might accidentally kill other important servers or processes
|
|
@@ -12,6 +12,7 @@ from typing import (
|
|
|
12
12
|
|
|
13
13
|
from openhands.sdk.context.condenser.base import CondenserBase
|
|
14
14
|
from openhands.sdk.context.view import View
|
|
15
|
+
from openhands.sdk.conversation.types import ConversationTokenCallbackType
|
|
15
16
|
from openhands.sdk.event.base import Event, LLMConvertibleEvent
|
|
16
17
|
from openhands.sdk.event.condenser import Condensation
|
|
17
18
|
from openhands.sdk.llm import LLM, LLMResponse, Message
|
|
@@ -182,6 +183,7 @@ def make_llm_completion(
|
|
|
182
183
|
llm: LLM,
|
|
183
184
|
messages: list[Message],
|
|
184
185
|
tools: list[ToolDefinition] | None = None,
|
|
186
|
+
on_token: ConversationTokenCallbackType | None = None,
|
|
185
187
|
) -> LLMResponse:
|
|
186
188
|
"""Make an LLM completion call with the provided messages and tools.
|
|
187
189
|
|
|
@@ -189,6 +191,7 @@ def make_llm_completion(
|
|
|
189
191
|
llm: The LLM instance to use for completion
|
|
190
192
|
messages: The messages to send to the LLM
|
|
191
193
|
tools: Optional list of tools to provide to the LLM
|
|
194
|
+
on_token: Optional callback for streaming token updates
|
|
192
195
|
|
|
193
196
|
Returns:
|
|
194
197
|
LLMResponse from the LLM completion call
|
|
@@ -200,10 +203,12 @@ def make_llm_completion(
|
|
|
200
203
|
include=None,
|
|
201
204
|
store=False,
|
|
202
205
|
add_security_risk_prediction=True,
|
|
206
|
+
on_token=on_token,
|
|
203
207
|
)
|
|
204
208
|
else:
|
|
205
209
|
return llm.completion(
|
|
206
210
|
messages=messages,
|
|
207
211
|
tools=tools or [],
|
|
208
212
|
add_security_risk_prediction=True,
|
|
213
|
+
on_token=on_token,
|
|
209
214
|
)
|
|
@@ -6,6 +6,7 @@ from openhands.sdk.context.prompts import render_template
|
|
|
6
6
|
from openhands.sdk.context.skills import (
|
|
7
7
|
Skill,
|
|
8
8
|
SkillKnowledge,
|
|
9
|
+
load_public_skills,
|
|
9
10
|
load_user_skills,
|
|
10
11
|
)
|
|
11
12
|
from openhands.sdk.llm import Message, TextContent
|
|
@@ -56,6 +57,14 @@ class AgentContext(BaseModel):
|
|
|
56
57
|
"and ~/.openhands/microagents/ (for backward compatibility). "
|
|
57
58
|
),
|
|
58
59
|
)
|
|
60
|
+
load_public_skills: bool = Field(
|
|
61
|
+
default=False,
|
|
62
|
+
description=(
|
|
63
|
+
"Whether to automatically load skills from the public OpenHands "
|
|
64
|
+
"skills repository at https://github.com/OpenHands/skills. "
|
|
65
|
+
"This allows you to get the latest skills without SDK updates."
|
|
66
|
+
),
|
|
67
|
+
)
|
|
59
68
|
|
|
60
69
|
@field_validator("skills")
|
|
61
70
|
@classmethod
|
|
@@ -93,6 +102,27 @@ class AgentContext(BaseModel):
|
|
|
93
102
|
|
|
94
103
|
return self
|
|
95
104
|
|
|
105
|
+
@model_validator(mode="after")
|
|
106
|
+
def _load_public_skills(self):
|
|
107
|
+
"""Load public skills from OpenHands skills repository if enabled."""
|
|
108
|
+
if not self.load_public_skills:
|
|
109
|
+
return self
|
|
110
|
+
try:
|
|
111
|
+
public_skills = load_public_skills()
|
|
112
|
+
# Merge public skills with explicit skills, avoiding duplicates
|
|
113
|
+
existing_names = {skill.name for skill in self.skills}
|
|
114
|
+
for public_skill in public_skills:
|
|
115
|
+
if public_skill.name not in existing_names:
|
|
116
|
+
self.skills.append(public_skill)
|
|
117
|
+
else:
|
|
118
|
+
logger.warning(
|
|
119
|
+
f"Skipping public skill '{public_skill.name}' "
|
|
120
|
+
f"(already in existing skills)"
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.warning(f"Failed to load public skills: {str(e)}")
|
|
124
|
+
return self
|
|
125
|
+
|
|
96
126
|
def get_system_message_suffix(self) -> str | None:
|
|
97
127
|
"""Get the system message with repo skill content and custom suffix.
|
|
98
128
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from openhands.sdk.context.skills.exceptions import SkillValidationError
|
|
2
2
|
from openhands.sdk.context.skills.skill import (
|
|
3
3
|
Skill,
|
|
4
|
+
load_public_skills,
|
|
4
5
|
load_skills_from_dir,
|
|
5
6
|
load_user_skills,
|
|
6
7
|
)
|
|
@@ -20,5 +21,6 @@ __all__ = [
|
|
|
20
21
|
"SkillKnowledge",
|
|
21
22
|
"load_skills_from_dir",
|
|
22
23
|
"load_user_skills",
|
|
24
|
+
"load_public_skills",
|
|
23
25
|
"SkillValidationError",
|
|
24
26
|
]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import re
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
3
5
|
from itertools import chain
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from typing import Annotated, ClassVar, Union
|
|
@@ -15,10 +17,15 @@ from openhands.sdk.context.skills.trigger import (
|
|
|
15
17
|
)
|
|
16
18
|
from openhands.sdk.context.skills.types import InputMetadata
|
|
17
19
|
from openhands.sdk.logger import get_logger
|
|
20
|
+
from openhands.sdk.utils import maybe_truncate
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
logger = get_logger(__name__)
|
|
21
24
|
|
|
25
|
+
# Maximum characters for third-party skill files (e.g., AGENTS.md, CLAUDE.md, GEMINI.md)
|
|
26
|
+
# These files are always active, so we want to keep them reasonably sized
|
|
27
|
+
THIRD_PARTY_SKILL_MAX_CHARS = 10_000
|
|
28
|
+
|
|
22
29
|
# Union type for all trigger types
|
|
23
30
|
TriggerType = Annotated[
|
|
24
31
|
KeywordTrigger | TaskTrigger,
|
|
@@ -71,6 +78,8 @@ class Skill(BaseModel):
|
|
|
71
78
|
".cursorrules": "cursorrules",
|
|
72
79
|
"agents.md": "agents",
|
|
73
80
|
"agent.md": "agents",
|
|
81
|
+
"claude.md": "claude",
|
|
82
|
+
"gemini.md": "gemini",
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
@classmethod
|
|
@@ -80,9 +89,30 @@ class Skill(BaseModel):
|
|
|
80
89
|
|
|
81
90
|
# Create Skill with None trigger (always active) if we recognized the file type
|
|
82
91
|
if skill_name is not None:
|
|
92
|
+
# Truncate content if it exceeds the limit
|
|
93
|
+
# Third-party files are always active, so we want to keep them
|
|
94
|
+
# reasonably sized
|
|
95
|
+
truncated_content = maybe_truncate(
|
|
96
|
+
file_content,
|
|
97
|
+
truncate_after=THIRD_PARTY_SKILL_MAX_CHARS,
|
|
98
|
+
truncate_notice=(
|
|
99
|
+
f"\n\n<TRUNCATED><NOTE>The file {path} exceeded the "
|
|
100
|
+
f"maximum length ({THIRD_PARTY_SKILL_MAX_CHARS} "
|
|
101
|
+
f"characters) and has been truncated. Only the "
|
|
102
|
+
f"beginning and end are shown. You can read the full "
|
|
103
|
+
f"file if needed.</NOTE>\n\n"
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if len(file_content) > THIRD_PARTY_SKILL_MAX_CHARS:
|
|
108
|
+
logger.warning(
|
|
109
|
+
f"Third-party skill file {path} ({len(file_content)} chars) "
|
|
110
|
+
f"exceeded limit ({THIRD_PARTY_SKILL_MAX_CHARS} chars), truncating"
|
|
111
|
+
)
|
|
112
|
+
|
|
83
113
|
return Skill(
|
|
84
114
|
name=skill_name,
|
|
85
|
-
content=
|
|
115
|
+
content=truncated_content,
|
|
86
116
|
source=str(path),
|
|
87
117
|
trigger=None,
|
|
88
118
|
)
|
|
@@ -366,3 +396,174 @@ def load_user_skills() -> list[Skill]:
|
|
|
366
396
|
f"Loaded {len(all_skills)} user skills: {[s.name for s in all_skills]}"
|
|
367
397
|
)
|
|
368
398
|
return all_skills
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# Public skills repository configuration
|
|
402
|
+
PUBLIC_SKILLS_REPO = "https://github.com/OpenHands/skills"
|
|
403
|
+
PUBLIC_SKILLS_BRANCH = "main"
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _get_skills_cache_dir() -> Path:
|
|
407
|
+
"""Get the local cache directory for public skills repository.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Path to the skills cache directory (~/.openhands/cache/skills).
|
|
411
|
+
"""
|
|
412
|
+
cache_dir = Path.home() / ".openhands" / "cache" / "skills"
|
|
413
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
414
|
+
return cache_dir
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _update_skills_repository(
|
|
418
|
+
repo_url: str,
|
|
419
|
+
branch: str,
|
|
420
|
+
cache_dir: Path,
|
|
421
|
+
) -> Path | None:
|
|
422
|
+
"""Clone or update the local skills repository.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
repo_url: URL of the skills repository.
|
|
426
|
+
branch: Branch name to use.
|
|
427
|
+
cache_dir: Directory where the repository should be cached.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Path to the local repository if successful, None otherwise.
|
|
431
|
+
"""
|
|
432
|
+
repo_path = cache_dir / "public-skills"
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
if repo_path.exists() and (repo_path / ".git").exists():
|
|
436
|
+
logger.debug(f"Updating skills repository at {repo_path}")
|
|
437
|
+
try:
|
|
438
|
+
subprocess.run(
|
|
439
|
+
["git", "fetch", "origin"],
|
|
440
|
+
cwd=repo_path,
|
|
441
|
+
check=True,
|
|
442
|
+
capture_output=True,
|
|
443
|
+
timeout=30,
|
|
444
|
+
)
|
|
445
|
+
subprocess.run(
|
|
446
|
+
["git", "reset", "--hard", f"origin/{branch}"],
|
|
447
|
+
cwd=repo_path,
|
|
448
|
+
check=True,
|
|
449
|
+
capture_output=True,
|
|
450
|
+
timeout=10,
|
|
451
|
+
)
|
|
452
|
+
logger.debug("Skills repository updated successfully")
|
|
453
|
+
except subprocess.TimeoutExpired:
|
|
454
|
+
logger.warning("Git pull timed out, using existing cached repository")
|
|
455
|
+
except subprocess.CalledProcessError as e:
|
|
456
|
+
logger.warning(
|
|
457
|
+
f"Failed to update repository: {e.stderr.decode()}, "
|
|
458
|
+
f"using existing cached version"
|
|
459
|
+
)
|
|
460
|
+
else:
|
|
461
|
+
logger.info(f"Cloning public skills repository from {repo_url}")
|
|
462
|
+
if repo_path.exists():
|
|
463
|
+
shutil.rmtree(repo_path)
|
|
464
|
+
|
|
465
|
+
subprocess.run(
|
|
466
|
+
[
|
|
467
|
+
"git",
|
|
468
|
+
"clone",
|
|
469
|
+
"--depth",
|
|
470
|
+
"1",
|
|
471
|
+
"--branch",
|
|
472
|
+
branch,
|
|
473
|
+
repo_url,
|
|
474
|
+
str(repo_path),
|
|
475
|
+
],
|
|
476
|
+
check=True,
|
|
477
|
+
capture_output=True,
|
|
478
|
+
timeout=60,
|
|
479
|
+
)
|
|
480
|
+
logger.debug(f"Skills repository cloned to {repo_path}")
|
|
481
|
+
|
|
482
|
+
return repo_path
|
|
483
|
+
|
|
484
|
+
except subprocess.TimeoutExpired:
|
|
485
|
+
logger.warning(f"Git operation timed out for {repo_url}")
|
|
486
|
+
return None
|
|
487
|
+
except subprocess.CalledProcessError as e:
|
|
488
|
+
logger.warning(
|
|
489
|
+
f"Failed to clone/update repository {repo_url}: {e.stderr.decode()}"
|
|
490
|
+
)
|
|
491
|
+
return None
|
|
492
|
+
except Exception as e:
|
|
493
|
+
logger.warning(f"Error managing skills repository: {str(e)}")
|
|
494
|
+
return None
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def load_public_skills(
|
|
498
|
+
repo_url: str = PUBLIC_SKILLS_REPO,
|
|
499
|
+
branch: str = PUBLIC_SKILLS_BRANCH,
|
|
500
|
+
) -> list[Skill]:
|
|
501
|
+
"""Load skills from the public OpenHands skills repository.
|
|
502
|
+
|
|
503
|
+
This function maintains a local git clone of the public skills registry at
|
|
504
|
+
https://github.com/OpenHands/skills. On first run, it clones the repository
|
|
505
|
+
to ~/.openhands/skills-cache/. On subsequent runs, it pulls the latest changes
|
|
506
|
+
to keep the skills up-to-date. This approach is more efficient than fetching
|
|
507
|
+
individual files via HTTP.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
repo_url: URL of the skills repository. Defaults to the official
|
|
511
|
+
OpenHands skills repository.
|
|
512
|
+
branch: Branch name to load skills from. Defaults to 'main'.
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
List of Skill objects loaded from the public repository.
|
|
516
|
+
Returns empty list if loading fails.
|
|
517
|
+
|
|
518
|
+
Example:
|
|
519
|
+
>>> from openhands.sdk.context import AgentContext
|
|
520
|
+
>>> from openhands.sdk.context.skills import load_public_skills
|
|
521
|
+
>>>
|
|
522
|
+
>>> # Load public skills
|
|
523
|
+
>>> public_skills = load_public_skills()
|
|
524
|
+
>>>
|
|
525
|
+
>>> # Use with AgentContext
|
|
526
|
+
>>> context = AgentContext(skills=public_skills)
|
|
527
|
+
"""
|
|
528
|
+
all_skills = []
|
|
529
|
+
|
|
530
|
+
try:
|
|
531
|
+
# Get or update the local repository
|
|
532
|
+
cache_dir = _get_skills_cache_dir()
|
|
533
|
+
repo_path = _update_skills_repository(repo_url, branch, cache_dir)
|
|
534
|
+
|
|
535
|
+
if repo_path is None:
|
|
536
|
+
logger.warning("Failed to access public skills repository")
|
|
537
|
+
return all_skills
|
|
538
|
+
|
|
539
|
+
# Load skills from the local repository
|
|
540
|
+
skills_dir = repo_path / "skills"
|
|
541
|
+
if not skills_dir.exists():
|
|
542
|
+
logger.warning(f"Skills directory not found in repository: {skills_dir}")
|
|
543
|
+
return all_skills
|
|
544
|
+
|
|
545
|
+
# Find all .md files in the skills directory
|
|
546
|
+
md_files = [f for f in skills_dir.rglob("*.md") if f.name != "README.md"]
|
|
547
|
+
|
|
548
|
+
logger.info(f"Found {len(md_files)} skill files in public skills repository")
|
|
549
|
+
|
|
550
|
+
# Load each skill file
|
|
551
|
+
for skill_file in md_files:
|
|
552
|
+
try:
|
|
553
|
+
skill = Skill.load(
|
|
554
|
+
path=skill_file,
|
|
555
|
+
skill_dir=repo_path,
|
|
556
|
+
)
|
|
557
|
+
all_skills.append(skill)
|
|
558
|
+
logger.debug(f"Loaded public skill: {skill.name}")
|
|
559
|
+
except Exception as e:
|
|
560
|
+
logger.warning(f"Failed to load skill from {skill_file.name}: {str(e)}")
|
|
561
|
+
continue
|
|
562
|
+
|
|
563
|
+
except Exception as e:
|
|
564
|
+
logger.warning(f"Failed to load public skills from {repo_url}: {str(e)}")
|
|
565
|
+
|
|
566
|
+
logger.info(
|
|
567
|
+
f"Loaded {len(all_skills)} public skills: {[s.name for s in all_skills]}"
|
|
568
|
+
)
|
|
569
|
+
return all_skills
|
|
@@ -11,7 +11,10 @@ from openhands.sdk.conversation.state import (
|
|
|
11
11
|
ConversationState,
|
|
12
12
|
)
|
|
13
13
|
from openhands.sdk.conversation.stuck_detector import StuckDetector
|
|
14
|
-
from openhands.sdk.conversation.types import
|
|
14
|
+
from openhands.sdk.conversation.types import (
|
|
15
|
+
ConversationCallbackType,
|
|
16
|
+
ConversationTokenCallbackType,
|
|
17
|
+
)
|
|
15
18
|
from openhands.sdk.conversation.visualizer import (
|
|
16
19
|
ConversationVisualizerBase,
|
|
17
20
|
DefaultConversationVisualizer,
|
|
@@ -24,6 +27,7 @@ __all__ = [
|
|
|
24
27
|
"ConversationState",
|
|
25
28
|
"ConversationExecutionStatus",
|
|
26
29
|
"ConversationCallbackType",
|
|
30
|
+
"ConversationTokenCallbackType",
|
|
27
31
|
"DefaultConversationVisualizer",
|
|
28
32
|
"ConversationVisualizerBase",
|
|
29
33
|
"SecretRegistry",
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from collections.abc import Iterable, Mapping
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING, Protocol
|
|
4
|
+
from typing import TYPE_CHECKING, Protocol, TypeVar, cast
|
|
5
5
|
|
|
6
6
|
from openhands.sdk.conversation.conversation_stats import ConversationStats
|
|
7
7
|
from openhands.sdk.conversation.events_list_base import EventsListBase
|
|
8
8
|
from openhands.sdk.conversation.secret_registry import SecretValue
|
|
9
|
-
from openhands.sdk.conversation.types import
|
|
9
|
+
from openhands.sdk.conversation.types import (
|
|
10
|
+
ConversationCallbackType,
|
|
11
|
+
ConversationID,
|
|
12
|
+
ConversationTokenCallbackType,
|
|
13
|
+
)
|
|
10
14
|
from openhands.sdk.llm.llm import LLM
|
|
11
15
|
from openhands.sdk.llm.message import Message
|
|
12
16
|
from openhands.sdk.observability.laminar import (
|
|
@@ -27,6 +31,13 @@ if TYPE_CHECKING:
|
|
|
27
31
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
28
32
|
|
|
29
33
|
|
|
34
|
+
CallbackType = TypeVar(
|
|
35
|
+
"CallbackType",
|
|
36
|
+
ConversationCallbackType,
|
|
37
|
+
ConversationTokenCallbackType,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
30
41
|
class ConversationStateProtocol(Protocol):
|
|
31
42
|
"""Protocol defining the interface for conversation state objects."""
|
|
32
43
|
|
|
@@ -235,9 +246,7 @@ class BaseConversation(ABC):
|
|
|
235
246
|
...
|
|
236
247
|
|
|
237
248
|
@staticmethod
|
|
238
|
-
def compose_callbacks(
|
|
239
|
-
callbacks: Iterable[ConversationCallbackType],
|
|
240
|
-
) -> ConversationCallbackType:
|
|
249
|
+
def compose_callbacks(callbacks: Iterable[CallbackType]) -> CallbackType:
|
|
241
250
|
"""Compose multiple callbacks into a single callback function.
|
|
242
251
|
|
|
243
252
|
Args:
|
|
@@ -252,4 +261,4 @@ class BaseConversation(ABC):
|
|
|
252
261
|
if cb:
|
|
253
262
|
cb(event)
|
|
254
263
|
|
|
255
|
-
return composed
|
|
264
|
+
return cast(CallbackType, composed)
|
|
@@ -4,7 +4,11 @@ from typing import TYPE_CHECKING, Self, overload
|
|
|
4
4
|
from openhands.sdk.agent.base import AgentBase
|
|
5
5
|
from openhands.sdk.conversation.base import BaseConversation
|
|
6
6
|
from openhands.sdk.conversation.secret_registry import SecretValue
|
|
7
|
-
from openhands.sdk.conversation.types import
|
|
7
|
+
from openhands.sdk.conversation.types import (
|
|
8
|
+
ConversationCallbackType,
|
|
9
|
+
ConversationID,
|
|
10
|
+
ConversationTokenCallbackType,
|
|
11
|
+
)
|
|
8
12
|
from openhands.sdk.conversation.visualizer import (
|
|
9
13
|
ConversationVisualizerBase,
|
|
10
14
|
DefaultConversationVisualizer,
|
|
@@ -49,6 +53,7 @@ class Conversation:
|
|
|
49
53
|
persistence_dir: str | Path | None = None,
|
|
50
54
|
conversation_id: ConversationID | None = None,
|
|
51
55
|
callbacks: list[ConversationCallbackType] | None = None,
|
|
56
|
+
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
52
57
|
max_iteration_per_run: int = 500,
|
|
53
58
|
stuck_detection: bool = True,
|
|
54
59
|
visualizer: (
|
|
@@ -65,6 +70,7 @@ class Conversation:
|
|
|
65
70
|
workspace: RemoteWorkspace,
|
|
66
71
|
conversation_id: ConversationID | None = None,
|
|
67
72
|
callbacks: list[ConversationCallbackType] | None = None,
|
|
73
|
+
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
68
74
|
max_iteration_per_run: int = 500,
|
|
69
75
|
stuck_detection: bool = True,
|
|
70
76
|
visualizer: (
|
|
@@ -81,6 +87,7 @@ class Conversation:
|
|
|
81
87
|
persistence_dir: str | Path | None = None,
|
|
82
88
|
conversation_id: ConversationID | None = None,
|
|
83
89
|
callbacks: list[ConversationCallbackType] | None = None,
|
|
90
|
+
token_callbacks: list[ConversationTokenCallbackType] | None = None,
|
|
84
91
|
max_iteration_per_run: int = 500,
|
|
85
92
|
stuck_detection: bool = True,
|
|
86
93
|
visualizer: (
|
|
@@ -104,6 +111,7 @@ class Conversation:
|
|
|
104
111
|
agent=agent,
|
|
105
112
|
conversation_id=conversation_id,
|
|
106
113
|
callbacks=callbacks,
|
|
114
|
+
token_callbacks=token_callbacks,
|
|
107
115
|
max_iteration_per_run=max_iteration_per_run,
|
|
108
116
|
stuck_detection=stuck_detection,
|
|
109
117
|
visualizer=visualizer,
|
|
@@ -115,6 +123,7 @@ class Conversation:
|
|
|
115
123
|
agent=agent,
|
|
116
124
|
conversation_id=conversation_id,
|
|
117
125
|
callbacks=callbacks,
|
|
126
|
+
token_callbacks=token_callbacks,
|
|
118
127
|
max_iteration_per_run=max_iteration_per_run,
|
|
119
128
|
stuck_detection=stuck_detection,
|
|
120
129
|
visualizer=visualizer,
|