openhands-sdk 1.7.3__py3-none-any.whl
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/__init__.py +111 -0
- openhands/sdk/agent/__init__.py +8 -0
- openhands/sdk/agent/agent.py +650 -0
- openhands/sdk/agent/base.py +457 -0
- openhands/sdk/agent/prompts/in_context_learning_example.j2 +169 -0
- openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +3 -0
- openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +3 -0
- openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +1 -0
- openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +2 -0
- openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +3 -0
- openhands/sdk/agent/prompts/security_policy.j2 +22 -0
- openhands/sdk/agent/prompts/security_risk_assessment.j2 +21 -0
- openhands/sdk/agent/prompts/self_documentation.j2 +15 -0
- openhands/sdk/agent/prompts/system_prompt.j2 +132 -0
- openhands/sdk/agent/prompts/system_prompt_interactive.j2 +14 -0
- openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +40 -0
- openhands/sdk/agent/prompts/system_prompt_planning.j2 +40 -0
- openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +122 -0
- openhands/sdk/agent/utils.py +228 -0
- openhands/sdk/context/__init__.py +28 -0
- openhands/sdk/context/agent_context.py +264 -0
- openhands/sdk/context/condenser/__init__.py +18 -0
- openhands/sdk/context/condenser/base.py +100 -0
- openhands/sdk/context/condenser/llm_summarizing_condenser.py +248 -0
- openhands/sdk/context/condenser/no_op_condenser.py +14 -0
- openhands/sdk/context/condenser/pipeline_condenser.py +56 -0
- openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +59 -0
- openhands/sdk/context/condenser/utils.py +149 -0
- openhands/sdk/context/prompts/__init__.py +6 -0
- openhands/sdk/context/prompts/prompt.py +114 -0
- openhands/sdk/context/prompts/templates/ask_agent_template.j2 +11 -0
- openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +8 -0
- openhands/sdk/context/prompts/templates/system_message_suffix.j2 +32 -0
- openhands/sdk/context/skills/__init__.py +28 -0
- openhands/sdk/context/skills/exceptions.py +11 -0
- openhands/sdk/context/skills/skill.py +720 -0
- openhands/sdk/context/skills/trigger.py +36 -0
- openhands/sdk/context/skills/types.py +48 -0
- openhands/sdk/context/view.py +503 -0
- openhands/sdk/conversation/__init__.py +40 -0
- openhands/sdk/conversation/base.py +281 -0
- openhands/sdk/conversation/conversation.py +152 -0
- openhands/sdk/conversation/conversation_stats.py +85 -0
- openhands/sdk/conversation/event_store.py +157 -0
- openhands/sdk/conversation/events_list_base.py +17 -0
- openhands/sdk/conversation/exceptions.py +50 -0
- openhands/sdk/conversation/fifo_lock.py +133 -0
- openhands/sdk/conversation/impl/__init__.py +5 -0
- openhands/sdk/conversation/impl/local_conversation.py +665 -0
- openhands/sdk/conversation/impl/remote_conversation.py +956 -0
- openhands/sdk/conversation/persistence_const.py +9 -0
- openhands/sdk/conversation/response_utils.py +41 -0
- openhands/sdk/conversation/secret_registry.py +126 -0
- openhands/sdk/conversation/serialization_diff.py +0 -0
- openhands/sdk/conversation/state.py +392 -0
- openhands/sdk/conversation/stuck_detector.py +311 -0
- openhands/sdk/conversation/title_utils.py +191 -0
- openhands/sdk/conversation/types.py +45 -0
- openhands/sdk/conversation/visualizer/__init__.py +12 -0
- openhands/sdk/conversation/visualizer/base.py +67 -0
- openhands/sdk/conversation/visualizer/default.py +373 -0
- openhands/sdk/critic/__init__.py +15 -0
- openhands/sdk/critic/base.py +38 -0
- openhands/sdk/critic/impl/__init__.py +12 -0
- openhands/sdk/critic/impl/agent_finished.py +83 -0
- openhands/sdk/critic/impl/empty_patch.py +49 -0
- openhands/sdk/critic/impl/pass_critic.py +42 -0
- openhands/sdk/event/__init__.py +42 -0
- openhands/sdk/event/base.py +149 -0
- openhands/sdk/event/condenser.py +82 -0
- openhands/sdk/event/conversation_error.py +25 -0
- openhands/sdk/event/conversation_state.py +104 -0
- openhands/sdk/event/llm_completion_log.py +39 -0
- openhands/sdk/event/llm_convertible/__init__.py +20 -0
- openhands/sdk/event/llm_convertible/action.py +139 -0
- openhands/sdk/event/llm_convertible/message.py +142 -0
- openhands/sdk/event/llm_convertible/observation.py +141 -0
- openhands/sdk/event/llm_convertible/system.py +61 -0
- openhands/sdk/event/token.py +16 -0
- openhands/sdk/event/types.py +11 -0
- openhands/sdk/event/user_action.py +21 -0
- openhands/sdk/git/exceptions.py +43 -0
- openhands/sdk/git/git_changes.py +249 -0
- openhands/sdk/git/git_diff.py +129 -0
- openhands/sdk/git/models.py +21 -0
- openhands/sdk/git/utils.py +189 -0
- openhands/sdk/hooks/__init__.py +30 -0
- openhands/sdk/hooks/config.py +180 -0
- openhands/sdk/hooks/conversation_hooks.py +227 -0
- openhands/sdk/hooks/executor.py +155 -0
- openhands/sdk/hooks/manager.py +170 -0
- openhands/sdk/hooks/types.py +40 -0
- openhands/sdk/io/__init__.py +6 -0
- openhands/sdk/io/base.py +48 -0
- openhands/sdk/io/cache.py +85 -0
- openhands/sdk/io/local.py +119 -0
- openhands/sdk/io/memory.py +54 -0
- openhands/sdk/llm/__init__.py +45 -0
- openhands/sdk/llm/exceptions/__init__.py +45 -0
- openhands/sdk/llm/exceptions/classifier.py +50 -0
- openhands/sdk/llm/exceptions/mapping.py +54 -0
- openhands/sdk/llm/exceptions/types.py +101 -0
- openhands/sdk/llm/llm.py +1140 -0
- openhands/sdk/llm/llm_registry.py +122 -0
- openhands/sdk/llm/llm_response.py +59 -0
- openhands/sdk/llm/message.py +656 -0
- openhands/sdk/llm/mixins/fn_call_converter.py +1288 -0
- openhands/sdk/llm/mixins/non_native_fc.py +97 -0
- openhands/sdk/llm/options/__init__.py +1 -0
- openhands/sdk/llm/options/chat_options.py +93 -0
- openhands/sdk/llm/options/common.py +19 -0
- openhands/sdk/llm/options/responses_options.py +67 -0
- openhands/sdk/llm/router/__init__.py +10 -0
- openhands/sdk/llm/router/base.py +117 -0
- openhands/sdk/llm/router/impl/multimodal.py +76 -0
- openhands/sdk/llm/router/impl/random.py +22 -0
- openhands/sdk/llm/streaming.py +9 -0
- openhands/sdk/llm/utils/metrics.py +312 -0
- openhands/sdk/llm/utils/model_features.py +192 -0
- openhands/sdk/llm/utils/model_info.py +90 -0
- openhands/sdk/llm/utils/model_prompt_spec.py +98 -0
- openhands/sdk/llm/utils/retry_mixin.py +128 -0
- openhands/sdk/llm/utils/telemetry.py +362 -0
- openhands/sdk/llm/utils/unverified_models.py +156 -0
- openhands/sdk/llm/utils/verified_models.py +65 -0
- openhands/sdk/logger/__init__.py +22 -0
- openhands/sdk/logger/logger.py +195 -0
- openhands/sdk/logger/rolling.py +113 -0
- openhands/sdk/mcp/__init__.py +24 -0
- openhands/sdk/mcp/client.py +76 -0
- openhands/sdk/mcp/definition.py +106 -0
- openhands/sdk/mcp/exceptions.py +19 -0
- openhands/sdk/mcp/tool.py +270 -0
- openhands/sdk/mcp/utils.py +83 -0
- openhands/sdk/observability/__init__.py +4 -0
- openhands/sdk/observability/laminar.py +166 -0
- openhands/sdk/observability/utils.py +20 -0
- openhands/sdk/py.typed +0 -0
- openhands/sdk/secret/__init__.py +19 -0
- openhands/sdk/secret/secrets.py +92 -0
- openhands/sdk/security/__init__.py +6 -0
- openhands/sdk/security/analyzer.py +111 -0
- openhands/sdk/security/confirmation_policy.py +61 -0
- openhands/sdk/security/llm_analyzer.py +29 -0
- openhands/sdk/security/risk.py +100 -0
- openhands/sdk/tool/__init__.py +34 -0
- openhands/sdk/tool/builtins/__init__.py +34 -0
- openhands/sdk/tool/builtins/finish.py +106 -0
- openhands/sdk/tool/builtins/think.py +117 -0
- openhands/sdk/tool/registry.py +184 -0
- openhands/sdk/tool/schema.py +286 -0
- openhands/sdk/tool/spec.py +39 -0
- openhands/sdk/tool/tool.py +481 -0
- openhands/sdk/utils/__init__.py +22 -0
- openhands/sdk/utils/async_executor.py +115 -0
- openhands/sdk/utils/async_utils.py +39 -0
- openhands/sdk/utils/cipher.py +68 -0
- openhands/sdk/utils/command.py +90 -0
- openhands/sdk/utils/deprecation.py +166 -0
- openhands/sdk/utils/github.py +44 -0
- openhands/sdk/utils/json.py +48 -0
- openhands/sdk/utils/models.py +570 -0
- openhands/sdk/utils/paging.py +63 -0
- openhands/sdk/utils/pydantic_diff.py +85 -0
- openhands/sdk/utils/pydantic_secrets.py +64 -0
- openhands/sdk/utils/truncate.py +117 -0
- openhands/sdk/utils/visualize.py +58 -0
- openhands/sdk/workspace/__init__.py +17 -0
- openhands/sdk/workspace/base.py +158 -0
- openhands/sdk/workspace/local.py +189 -0
- openhands/sdk/workspace/models.py +35 -0
- openhands/sdk/workspace/remote/__init__.py +8 -0
- openhands/sdk/workspace/remote/async_remote_workspace.py +149 -0
- openhands/sdk/workspace/remote/base.py +164 -0
- openhands/sdk/workspace/remote/remote_workspace_mixin.py +323 -0
- openhands/sdk/workspace/workspace.py +49 -0
- openhands_sdk-1.7.3.dist-info/METADATA +17 -0
- openhands_sdk-1.7.3.dist-info/RECORD +180 -0
- openhands_sdk-1.7.3.dist-info/WHEEL +5 -0
- openhands_sdk-1.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import ClassVar
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
from openhands.sdk.llm.llm import LLM
|
|
8
|
+
from openhands.sdk.logger import get_logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RegistryEvent(BaseModel):
|
|
15
|
+
llm: LLM
|
|
16
|
+
|
|
17
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
18
|
+
arbitrary_types_allowed=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LLMRegistry:
|
|
23
|
+
"""A minimal LLM registry for managing LLM instances by usage ID.
|
|
24
|
+
|
|
25
|
+
This registry provides a simple way to manage multiple LLM instances,
|
|
26
|
+
avoiding the need to recreate LLMs with the same configuration.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
registry_id: str
|
|
30
|
+
retry_listener: Callable[[int, int], None] | None
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
retry_listener: Callable[[int, int], None] | None = None,
|
|
35
|
+
):
|
|
36
|
+
"""Initialize the LLM registry.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
retry_listener: Optional callback for retry events.
|
|
40
|
+
"""
|
|
41
|
+
self.registry_id = str(uuid4())
|
|
42
|
+
self.retry_listener = retry_listener
|
|
43
|
+
self._usage_to_llm: dict[str, LLM] = {}
|
|
44
|
+
self.subscriber: Callable[[RegistryEvent], None] | None = None
|
|
45
|
+
|
|
46
|
+
def subscribe(self, callback: Callable[[RegistryEvent], None]) -> None:
|
|
47
|
+
"""Subscribe to registry events.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
callback: Function to call when LLMs are created or updated.
|
|
51
|
+
"""
|
|
52
|
+
self.subscriber = callback
|
|
53
|
+
|
|
54
|
+
def notify(self, event: RegistryEvent) -> None:
|
|
55
|
+
"""Notify subscribers of registry events.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
event: The registry event to notify about.
|
|
59
|
+
"""
|
|
60
|
+
if self.subscriber:
|
|
61
|
+
try:
|
|
62
|
+
self.subscriber(event)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.warning(f"Failed to emit event: {e}")
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def usage_to_llm(self) -> dict[str, LLM]:
|
|
68
|
+
"""Access the internal usage-ID-to-LLM mapping."""
|
|
69
|
+
|
|
70
|
+
return self._usage_to_llm
|
|
71
|
+
|
|
72
|
+
def add(self, llm: LLM) -> None:
|
|
73
|
+
"""Add an LLM instance to the registry.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
llm: The LLM instance to register.
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
ValueError: If llm.usage_id already exists in the registry.
|
|
80
|
+
"""
|
|
81
|
+
usage_id = llm.usage_id
|
|
82
|
+
if usage_id in self._usage_to_llm:
|
|
83
|
+
message = (
|
|
84
|
+
f"Usage ID '{usage_id}' already exists in registry. "
|
|
85
|
+
"Use a different usage_id on the LLM or "
|
|
86
|
+
"call get() to retrieve the existing LLM."
|
|
87
|
+
)
|
|
88
|
+
raise ValueError(message)
|
|
89
|
+
|
|
90
|
+
self._usage_to_llm[usage_id] = llm
|
|
91
|
+
self.notify(RegistryEvent(llm=llm))
|
|
92
|
+
logger.debug(
|
|
93
|
+
f"[LLM registry {self.registry_id}]: Added LLM for usage {usage_id}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def get(self, usage_id: str) -> LLM:
|
|
97
|
+
"""Get an LLM instance from the registry.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
usage_id: Unique identifier for the LLM usage slot.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The LLM instance.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
KeyError: If usage_id is not found in the registry.
|
|
107
|
+
"""
|
|
108
|
+
if usage_id not in self._usage_to_llm:
|
|
109
|
+
raise KeyError(
|
|
110
|
+
f"Usage ID '{usage_id}' not found in registry. "
|
|
111
|
+
"Use add() to register an LLM first."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
logger.info(
|
|
115
|
+
f"[LLM registry {self.registry_id}]: Retrieved LLM for usage {usage_id}"
|
|
116
|
+
)
|
|
117
|
+
return self._usage_to_llm[usage_id]
|
|
118
|
+
|
|
119
|
+
def list_usage_ids(self) -> list[str]:
|
|
120
|
+
"""List all registered usage IDs."""
|
|
121
|
+
|
|
122
|
+
return list(self._usage_to_llm.keys())
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""LLMResponse type for LLM completion responses.
|
|
2
|
+
|
|
3
|
+
This module provides the LLMResponse type that wraps LLM completion responses
|
|
4
|
+
with OpenHands-native types, eliminating the need for consumers to work directly
|
|
5
|
+
with LiteLLM types.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import warnings
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
|
|
11
|
+
from litellm import ResponsesAPIResponse
|
|
12
|
+
from litellm.types.utils import ModelResponse
|
|
13
|
+
from pydantic import BaseModel, ConfigDict
|
|
14
|
+
|
|
15
|
+
from openhands.sdk.llm.message import Message
|
|
16
|
+
from openhands.sdk.llm.utils.metrics import MetricsSnapshot
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Suppress Pydantic serializer warnings from litellm
|
|
20
|
+
# These warnings occur when Pydantic serializes litellm's ModelResponse objects
|
|
21
|
+
# that have mismatched field counts, which is expected behavior in litellm
|
|
22
|
+
warnings.filterwarnings("ignore", message="Pydantic serializer warnings")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["LLMResponse"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LLMResponse(BaseModel):
|
|
29
|
+
"""Result of an LLM completion request.
|
|
30
|
+
|
|
31
|
+
This type provides a clean interface for LLM completion results, exposing
|
|
32
|
+
only OpenHands-native types to consumers while preserving access to the
|
|
33
|
+
raw LiteLLM response for internal use.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
message: The completion message converted to OpenHands Message type
|
|
37
|
+
metrics: Snapshot of metrics from the completion request
|
|
38
|
+
raw_response: The original LiteLLM response (ModelResponse or
|
|
39
|
+
ResponsesAPIResponse) for internal use
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
message: Message
|
|
43
|
+
metrics: MetricsSnapshot
|
|
44
|
+
raw_response: ModelResponse | ResponsesAPIResponse
|
|
45
|
+
|
|
46
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def id(self) -> str:
|
|
50
|
+
"""Get the response ID from the underlying LLM response.
|
|
51
|
+
|
|
52
|
+
This property provides a clean interface to access the response ID,
|
|
53
|
+
supporting both completion mode (ModelResponse) and response API modes
|
|
54
|
+
(ResponsesAPIResponse).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The response ID from the LLM response
|
|
58
|
+
"""
|
|
59
|
+
return self.raw_response.id
|