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,50 @@
|
|
|
1
|
+
from openhands.sdk.conversation.types import ConversationID
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
ISSUE_URL = "https://github.com/OpenHands/software-agent-sdk/issues/new"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConversationRunError(RuntimeError):
|
|
8
|
+
"""Raised when a conversation run fails.
|
|
9
|
+
|
|
10
|
+
Carries the conversation_id and persistence_dir to make resuming/debugging
|
|
11
|
+
easier while preserving the original exception via exception chaining.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
conversation_id: ConversationID
|
|
15
|
+
persistence_dir: str | None
|
|
16
|
+
original_exception: BaseException
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
conversation_id: ConversationID,
|
|
21
|
+
original_exception: BaseException,
|
|
22
|
+
persistence_dir: str | None = None,
|
|
23
|
+
message: str | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
self.conversation_id = conversation_id
|
|
26
|
+
self.persistence_dir = persistence_dir
|
|
27
|
+
self.original_exception = original_exception
|
|
28
|
+
default_msg = self._build_error_message(
|
|
29
|
+
conversation_id, original_exception, persistence_dir
|
|
30
|
+
)
|
|
31
|
+
super().__init__(message or default_msg)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _build_error_message(
|
|
35
|
+
conversation_id: ConversationID,
|
|
36
|
+
original_exception: BaseException,
|
|
37
|
+
persistence_dir: str | None,
|
|
38
|
+
) -> str:
|
|
39
|
+
"""Build a detailed error message with debugging information."""
|
|
40
|
+
lines = [
|
|
41
|
+
f"Conversation run failed for id={conversation_id}: {original_exception}",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
if persistence_dir:
|
|
45
|
+
lines.append(f"\nConversation logs are stored at: {persistence_dir}")
|
|
46
|
+
lines.append("\nTo help debug this issue, please file a bug report at:")
|
|
47
|
+
lines.append(f" {ISSUE_URL}")
|
|
48
|
+
lines.append("and attach the conversation logs from the directory above.")
|
|
49
|
+
|
|
50
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FIFO Lock implementation that guarantees first-in-first-out access ordering.
|
|
3
|
+
|
|
4
|
+
This provides fair lock access where threads acquire the lock in the exact order
|
|
5
|
+
they requested it, preventing starvation that can occur with standard RLock.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from collections import deque
|
|
11
|
+
from typing import Any, Self
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FIFOLock:
|
|
15
|
+
"""
|
|
16
|
+
A reentrant lock that guarantees FIFO (first-in-first-out) access ordering.
|
|
17
|
+
|
|
18
|
+
Unlike Python's standard RLock, this lock ensures that threads acquire
|
|
19
|
+
the lock in the exact order they requested it, providing fairness and
|
|
20
|
+
preventing lock starvation.
|
|
21
|
+
|
|
22
|
+
Features:
|
|
23
|
+
- Reentrant: Same thread can acquire multiple times
|
|
24
|
+
- FIFO ordering: Threads get lock in request order
|
|
25
|
+
- Context manager support: Use with 'with' statement
|
|
26
|
+
- Thread-safe: Safe for concurrent access
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_mutex: threading.Lock
|
|
30
|
+
_count: int
|
|
31
|
+
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
self._mutex = threading.Lock() # Protects internal state
|
|
34
|
+
self._waiters: deque[threading.Condition] = (
|
|
35
|
+
deque()
|
|
36
|
+
) # FIFO queue of waiting threads
|
|
37
|
+
self._owner: int | None = None # Current lock owner thread ID
|
|
38
|
+
self._count = 0 # Reentrancy counter
|
|
39
|
+
|
|
40
|
+
def acquire(self, blocking: bool = True, timeout: float = -1) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Acquire the lock.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
blocking: If True, block until lock is acquired. If False, return
|
|
46
|
+
immediately.
|
|
47
|
+
timeout: Maximum time to wait for lock (ignored if blocking=False).
|
|
48
|
+
-1 means wait indefinitely.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
True if lock was acquired, False otherwise.
|
|
52
|
+
"""
|
|
53
|
+
ident = threading.get_ident()
|
|
54
|
+
start = time.monotonic()
|
|
55
|
+
|
|
56
|
+
with self._mutex:
|
|
57
|
+
# Reentrant case
|
|
58
|
+
if self._owner == ident:
|
|
59
|
+
self._count += 1
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
if self._owner is None and not self._waiters:
|
|
63
|
+
self._owner = ident
|
|
64
|
+
self._count = 1
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
if not blocking:
|
|
68
|
+
# Give up immediately
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
# Add to wait queue
|
|
72
|
+
me = threading.Condition(self._mutex)
|
|
73
|
+
self._waiters.append(me)
|
|
74
|
+
|
|
75
|
+
while True:
|
|
76
|
+
# If I'm at the front of the queue and nobody owns it → acquire
|
|
77
|
+
if self._waiters[0] is me and self._owner is None:
|
|
78
|
+
self._waiters.popleft()
|
|
79
|
+
self._owner = ident
|
|
80
|
+
self._count = 1
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
if timeout >= 0:
|
|
84
|
+
remaining = timeout - (time.monotonic() - start)
|
|
85
|
+
if remaining <= 0:
|
|
86
|
+
self._waiters.remove(me)
|
|
87
|
+
return False
|
|
88
|
+
me.wait(remaining)
|
|
89
|
+
else:
|
|
90
|
+
me.wait()
|
|
91
|
+
|
|
92
|
+
def release(self) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Release the lock.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
RuntimeError: If the current thread doesn't own the lock.
|
|
98
|
+
"""
|
|
99
|
+
ident = threading.get_ident()
|
|
100
|
+
with self._mutex:
|
|
101
|
+
if self._owner != ident:
|
|
102
|
+
raise RuntimeError("Cannot release lock not owned by current thread")
|
|
103
|
+
assert self._count >= 1, (
|
|
104
|
+
"When releasing the resource, the count must be >= 1"
|
|
105
|
+
)
|
|
106
|
+
self._count -= 1
|
|
107
|
+
if self._count == 0:
|
|
108
|
+
self._owner = None
|
|
109
|
+
if self._waiters:
|
|
110
|
+
self._waiters[0].notify()
|
|
111
|
+
|
|
112
|
+
def __enter__(self: Self) -> Self:
|
|
113
|
+
"""Context manager entry."""
|
|
114
|
+
self.acquire()
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
118
|
+
"""Context manager exit."""
|
|
119
|
+
self.release()
|
|
120
|
+
|
|
121
|
+
def locked(self) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Return True if the lock is currently held by any thread.
|
|
124
|
+
"""
|
|
125
|
+
with self._mutex:
|
|
126
|
+
return self._owner is not None
|
|
127
|
+
|
|
128
|
+
def owned(self) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Return True if the lock is currently held by the calling thread.
|
|
131
|
+
"""
|
|
132
|
+
with self._mutex:
|
|
133
|
+
return self._owner == threading.get_ident()
|