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.
Files changed (180) hide show
  1. openhands/sdk/__init__.py +111 -0
  2. openhands/sdk/agent/__init__.py +8 -0
  3. openhands/sdk/agent/agent.py +650 -0
  4. openhands/sdk/agent/base.py +457 -0
  5. openhands/sdk/agent/prompts/in_context_learning_example.j2 +169 -0
  6. openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +3 -0
  7. openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +3 -0
  8. openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +1 -0
  9. openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +2 -0
  10. openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +3 -0
  11. openhands/sdk/agent/prompts/security_policy.j2 +22 -0
  12. openhands/sdk/agent/prompts/security_risk_assessment.j2 +21 -0
  13. openhands/sdk/agent/prompts/self_documentation.j2 +15 -0
  14. openhands/sdk/agent/prompts/system_prompt.j2 +132 -0
  15. openhands/sdk/agent/prompts/system_prompt_interactive.j2 +14 -0
  16. openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +40 -0
  17. openhands/sdk/agent/prompts/system_prompt_planning.j2 +40 -0
  18. openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +122 -0
  19. openhands/sdk/agent/utils.py +228 -0
  20. openhands/sdk/context/__init__.py +28 -0
  21. openhands/sdk/context/agent_context.py +264 -0
  22. openhands/sdk/context/condenser/__init__.py +18 -0
  23. openhands/sdk/context/condenser/base.py +100 -0
  24. openhands/sdk/context/condenser/llm_summarizing_condenser.py +248 -0
  25. openhands/sdk/context/condenser/no_op_condenser.py +14 -0
  26. openhands/sdk/context/condenser/pipeline_condenser.py +56 -0
  27. openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +59 -0
  28. openhands/sdk/context/condenser/utils.py +149 -0
  29. openhands/sdk/context/prompts/__init__.py +6 -0
  30. openhands/sdk/context/prompts/prompt.py +114 -0
  31. openhands/sdk/context/prompts/templates/ask_agent_template.j2 +11 -0
  32. openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +8 -0
  33. openhands/sdk/context/prompts/templates/system_message_suffix.j2 +32 -0
  34. openhands/sdk/context/skills/__init__.py +28 -0
  35. openhands/sdk/context/skills/exceptions.py +11 -0
  36. openhands/sdk/context/skills/skill.py +720 -0
  37. openhands/sdk/context/skills/trigger.py +36 -0
  38. openhands/sdk/context/skills/types.py +48 -0
  39. openhands/sdk/context/view.py +503 -0
  40. openhands/sdk/conversation/__init__.py +40 -0
  41. openhands/sdk/conversation/base.py +281 -0
  42. openhands/sdk/conversation/conversation.py +152 -0
  43. openhands/sdk/conversation/conversation_stats.py +85 -0
  44. openhands/sdk/conversation/event_store.py +157 -0
  45. openhands/sdk/conversation/events_list_base.py +17 -0
  46. openhands/sdk/conversation/exceptions.py +50 -0
  47. openhands/sdk/conversation/fifo_lock.py +133 -0
  48. openhands/sdk/conversation/impl/__init__.py +5 -0
  49. openhands/sdk/conversation/impl/local_conversation.py +665 -0
  50. openhands/sdk/conversation/impl/remote_conversation.py +956 -0
  51. openhands/sdk/conversation/persistence_const.py +9 -0
  52. openhands/sdk/conversation/response_utils.py +41 -0
  53. openhands/sdk/conversation/secret_registry.py +126 -0
  54. openhands/sdk/conversation/serialization_diff.py +0 -0
  55. openhands/sdk/conversation/state.py +392 -0
  56. openhands/sdk/conversation/stuck_detector.py +311 -0
  57. openhands/sdk/conversation/title_utils.py +191 -0
  58. openhands/sdk/conversation/types.py +45 -0
  59. openhands/sdk/conversation/visualizer/__init__.py +12 -0
  60. openhands/sdk/conversation/visualizer/base.py +67 -0
  61. openhands/sdk/conversation/visualizer/default.py +373 -0
  62. openhands/sdk/critic/__init__.py +15 -0
  63. openhands/sdk/critic/base.py +38 -0
  64. openhands/sdk/critic/impl/__init__.py +12 -0
  65. openhands/sdk/critic/impl/agent_finished.py +83 -0
  66. openhands/sdk/critic/impl/empty_patch.py +49 -0
  67. openhands/sdk/critic/impl/pass_critic.py +42 -0
  68. openhands/sdk/event/__init__.py +42 -0
  69. openhands/sdk/event/base.py +149 -0
  70. openhands/sdk/event/condenser.py +82 -0
  71. openhands/sdk/event/conversation_error.py +25 -0
  72. openhands/sdk/event/conversation_state.py +104 -0
  73. openhands/sdk/event/llm_completion_log.py +39 -0
  74. openhands/sdk/event/llm_convertible/__init__.py +20 -0
  75. openhands/sdk/event/llm_convertible/action.py +139 -0
  76. openhands/sdk/event/llm_convertible/message.py +142 -0
  77. openhands/sdk/event/llm_convertible/observation.py +141 -0
  78. openhands/sdk/event/llm_convertible/system.py +61 -0
  79. openhands/sdk/event/token.py +16 -0
  80. openhands/sdk/event/types.py +11 -0
  81. openhands/sdk/event/user_action.py +21 -0
  82. openhands/sdk/git/exceptions.py +43 -0
  83. openhands/sdk/git/git_changes.py +249 -0
  84. openhands/sdk/git/git_diff.py +129 -0
  85. openhands/sdk/git/models.py +21 -0
  86. openhands/sdk/git/utils.py +189 -0
  87. openhands/sdk/hooks/__init__.py +30 -0
  88. openhands/sdk/hooks/config.py +180 -0
  89. openhands/sdk/hooks/conversation_hooks.py +227 -0
  90. openhands/sdk/hooks/executor.py +155 -0
  91. openhands/sdk/hooks/manager.py +170 -0
  92. openhands/sdk/hooks/types.py +40 -0
  93. openhands/sdk/io/__init__.py +6 -0
  94. openhands/sdk/io/base.py +48 -0
  95. openhands/sdk/io/cache.py +85 -0
  96. openhands/sdk/io/local.py +119 -0
  97. openhands/sdk/io/memory.py +54 -0
  98. openhands/sdk/llm/__init__.py +45 -0
  99. openhands/sdk/llm/exceptions/__init__.py +45 -0
  100. openhands/sdk/llm/exceptions/classifier.py +50 -0
  101. openhands/sdk/llm/exceptions/mapping.py +54 -0
  102. openhands/sdk/llm/exceptions/types.py +101 -0
  103. openhands/sdk/llm/llm.py +1140 -0
  104. openhands/sdk/llm/llm_registry.py +122 -0
  105. openhands/sdk/llm/llm_response.py +59 -0
  106. openhands/sdk/llm/message.py +656 -0
  107. openhands/sdk/llm/mixins/fn_call_converter.py +1288 -0
  108. openhands/sdk/llm/mixins/non_native_fc.py +97 -0
  109. openhands/sdk/llm/options/__init__.py +1 -0
  110. openhands/sdk/llm/options/chat_options.py +93 -0
  111. openhands/sdk/llm/options/common.py +19 -0
  112. openhands/sdk/llm/options/responses_options.py +67 -0
  113. openhands/sdk/llm/router/__init__.py +10 -0
  114. openhands/sdk/llm/router/base.py +117 -0
  115. openhands/sdk/llm/router/impl/multimodal.py +76 -0
  116. openhands/sdk/llm/router/impl/random.py +22 -0
  117. openhands/sdk/llm/streaming.py +9 -0
  118. openhands/sdk/llm/utils/metrics.py +312 -0
  119. openhands/sdk/llm/utils/model_features.py +192 -0
  120. openhands/sdk/llm/utils/model_info.py +90 -0
  121. openhands/sdk/llm/utils/model_prompt_spec.py +98 -0
  122. openhands/sdk/llm/utils/retry_mixin.py +128 -0
  123. openhands/sdk/llm/utils/telemetry.py +362 -0
  124. openhands/sdk/llm/utils/unverified_models.py +156 -0
  125. openhands/sdk/llm/utils/verified_models.py +65 -0
  126. openhands/sdk/logger/__init__.py +22 -0
  127. openhands/sdk/logger/logger.py +195 -0
  128. openhands/sdk/logger/rolling.py +113 -0
  129. openhands/sdk/mcp/__init__.py +24 -0
  130. openhands/sdk/mcp/client.py +76 -0
  131. openhands/sdk/mcp/definition.py +106 -0
  132. openhands/sdk/mcp/exceptions.py +19 -0
  133. openhands/sdk/mcp/tool.py +270 -0
  134. openhands/sdk/mcp/utils.py +83 -0
  135. openhands/sdk/observability/__init__.py +4 -0
  136. openhands/sdk/observability/laminar.py +166 -0
  137. openhands/sdk/observability/utils.py +20 -0
  138. openhands/sdk/py.typed +0 -0
  139. openhands/sdk/secret/__init__.py +19 -0
  140. openhands/sdk/secret/secrets.py +92 -0
  141. openhands/sdk/security/__init__.py +6 -0
  142. openhands/sdk/security/analyzer.py +111 -0
  143. openhands/sdk/security/confirmation_policy.py +61 -0
  144. openhands/sdk/security/llm_analyzer.py +29 -0
  145. openhands/sdk/security/risk.py +100 -0
  146. openhands/sdk/tool/__init__.py +34 -0
  147. openhands/sdk/tool/builtins/__init__.py +34 -0
  148. openhands/sdk/tool/builtins/finish.py +106 -0
  149. openhands/sdk/tool/builtins/think.py +117 -0
  150. openhands/sdk/tool/registry.py +184 -0
  151. openhands/sdk/tool/schema.py +286 -0
  152. openhands/sdk/tool/spec.py +39 -0
  153. openhands/sdk/tool/tool.py +481 -0
  154. openhands/sdk/utils/__init__.py +22 -0
  155. openhands/sdk/utils/async_executor.py +115 -0
  156. openhands/sdk/utils/async_utils.py +39 -0
  157. openhands/sdk/utils/cipher.py +68 -0
  158. openhands/sdk/utils/command.py +90 -0
  159. openhands/sdk/utils/deprecation.py +166 -0
  160. openhands/sdk/utils/github.py +44 -0
  161. openhands/sdk/utils/json.py +48 -0
  162. openhands/sdk/utils/models.py +570 -0
  163. openhands/sdk/utils/paging.py +63 -0
  164. openhands/sdk/utils/pydantic_diff.py +85 -0
  165. openhands/sdk/utils/pydantic_secrets.py +64 -0
  166. openhands/sdk/utils/truncate.py +117 -0
  167. openhands/sdk/utils/visualize.py +58 -0
  168. openhands/sdk/workspace/__init__.py +17 -0
  169. openhands/sdk/workspace/base.py +158 -0
  170. openhands/sdk/workspace/local.py +189 -0
  171. openhands/sdk/workspace/models.py +35 -0
  172. openhands/sdk/workspace/remote/__init__.py +8 -0
  173. openhands/sdk/workspace/remote/async_remote_workspace.py +149 -0
  174. openhands/sdk/workspace/remote/base.py +164 -0
  175. openhands/sdk/workspace/remote/remote_workspace_mixin.py +323 -0
  176. openhands/sdk/workspace/workspace.py +49 -0
  177. openhands_sdk-1.7.3.dist-info/METADATA +17 -0
  178. openhands_sdk-1.7.3.dist-info/RECORD +180 -0
  179. openhands_sdk-1.7.3.dist-info/WHEEL +5 -0
  180. 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()
@@ -0,0 +1,5 @@
1
+ from openhands.sdk.conversation.impl.local_conversation import LocalConversation
2
+ from openhands.sdk.conversation.impl.remote_conversation import RemoteConversation
3
+
4
+
5
+ __all__ = ["LocalConversation", "RemoteConversation"]