openhands-sdk 1.11.0__py3-none-any.whl → 1.11.1__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.
@@ -709,10 +709,16 @@ def load_user_skills() -> list[Skill]:
709
709
  def load_project_skills(work_dir: str | Path) -> list[Skill]:
710
710
  """Load skills from project-specific directories.
711
711
 
712
- Searches for skills in {work_dir}/.openhands/skills/ and
713
- {work_dir}/.openhands/microagents/ (legacy). Skills from both
714
- directories are merged, with skills/ taking precedence for
715
- duplicate names.
712
+ Searches for skills in {work_dir}/.agents/skills/,
713
+ {work_dir}/.openhands/skills/, and {work_dir}/.openhands/microagents/
714
+ (legacy). Skills are merged in priority order, with earlier directories
715
+ taking precedence for duplicate names.
716
+
717
+ Use .agents/skills for new skills. .openhands/skills is the legacy
718
+ OpenHands location, and .openhands/microagents is deprecated.
719
+
720
+ Example: If "my-skill" exists in both .agents/skills/ and
721
+ .openhands/skills/, the version from .agents/skills/ is used.
716
722
 
717
723
  Also loads third-party skill files (AGENTS.md, .cursorrules, etc.)
718
724
  directly from the work directory.
@@ -745,8 +751,10 @@ def load_project_skills(work_dir: str | Path) -> list[Skill]:
745
751
  except (SkillError, OSError) as e:
746
752
  logger.warning(f"Failed to load third-party skill from {path}: {e}")
747
753
 
748
- # Load project-specific skills from .openhands/skills and legacy microagents
754
+ # Load project-specific skills from .agents/skills, .openhands/skills,
755
+ # and legacy microagents (priority order; first wins for duplicates)
749
756
  project_skills_dirs = [
757
+ work_dir / ".agents" / "skills",
750
758
  work_dir / ".openhands" / "skills",
751
759
  work_dir / ".openhands" / "microagents", # Legacy support
752
760
  ]
@@ -23,6 +23,7 @@ from openhands.sdk.security.confirmation_policy import (
23
23
  ConfirmationPolicyBase,
24
24
  NeverConfirm,
25
25
  )
26
+ from openhands.sdk.tool.schema import Action, Observation
26
27
  from openhands.sdk.workspace.base import BaseWorkspace
27
28
 
28
29
 
@@ -267,6 +268,36 @@ class BaseConversation(ABC):
267
268
  """
268
269
  ...
269
270
 
271
+ @abstractmethod
272
+ def execute_tool(self, tool_name: str, action: Action) -> Observation:
273
+ """Execute a tool directly without going through the agent loop.
274
+
275
+ This method allows executing tools before or outside of the normal
276
+ conversation.run() flow. It handles agent initialization automatically,
277
+ so tools can be executed before the first run() call.
278
+
279
+ Note: This method bypasses the agent loop, including confirmation
280
+ policies and security analyzer checks. Callers are responsible for
281
+ applying any safeguards before executing potentially destructive tools.
282
+
283
+ This is useful for:
284
+ - Pre-run setup operations (e.g., indexing repositories)
285
+ - Manual tool execution for environment setup
286
+ - Testing tool behavior outside the agent loop
287
+
288
+ Args:
289
+ tool_name: The name of the tool to execute (e.g., "sleeptime_compute")
290
+ action: The action to pass to the tool executor
291
+
292
+ Returns:
293
+ The observation returned by the tool execution
294
+
295
+ Raises:
296
+ KeyError: If the tool is not found in the agent's tools
297
+ NotImplementedError: If the tool has no executor
298
+ """
299
+ ...
300
+
270
301
  @staticmethod
271
302
  def compose_callbacks(callbacks: Iterable[CallbackType]) -> CallbackType:
272
303
  """Compose multiple callbacks into a single callback function.
@@ -46,6 +46,7 @@ from openhands.sdk.security.analyzer import SecurityAnalyzerBase
46
46
  from openhands.sdk.security.confirmation_policy import (
47
47
  ConfirmationPolicyBase,
48
48
  )
49
+ from openhands.sdk.tool.schema import Action, Observation
49
50
  from openhands.sdk.utils.cipher import Cipher
50
51
  from openhands.sdk.workspace import LocalWorkspace
51
52
 
@@ -867,6 +868,49 @@ class LocalConversation(BaseConversation):
867
868
 
868
869
  logger.info("Condensation request processed")
869
870
 
871
+ def execute_tool(self, tool_name: str, action: Action) -> Observation:
872
+ """Execute a tool directly without going through the agent loop.
873
+
874
+ This method allows executing tools before or outside of the normal
875
+ conversation.run() flow. It handles agent initialization automatically,
876
+ so tools can be executed before the first run() call.
877
+
878
+ Note: This method bypasses the agent loop, including confirmation
879
+ policies and security analyzer checks. Callers are responsible for
880
+ applying any safeguards before executing potentially destructive tools.
881
+
882
+ This is useful for:
883
+ - Pre-run setup operations (e.g., indexing repositories)
884
+ - Manual tool execution for environment setup
885
+ - Testing tool behavior outside the agent loop
886
+
887
+ Args:
888
+ tool_name: The name of the tool to execute (e.g., "sleeptime_compute")
889
+ action: The action to pass to the tool executor
890
+
891
+ Returns:
892
+ The observation returned by the tool execution
893
+
894
+ Raises:
895
+ KeyError: If the tool is not found in the agent's tools
896
+ NotImplementedError: If the tool has no executor
897
+ """
898
+ # Ensure agent is initialized (loads plugins and initializes tools)
899
+ self._ensure_agent_ready()
900
+
901
+ # Get the tool from the agent's tools_map
902
+ tool = self.agent.tools_map.get(tool_name)
903
+ if tool is None:
904
+ available_tools = list(self.agent.tools_map.keys())
905
+ raise KeyError(
906
+ f"Tool '{tool_name}' not found. Available tools: {available_tools}"
907
+ )
908
+
909
+ # Execute the tool
910
+ if not tool.executor:
911
+ raise NotImplementedError(f"Tool '{tool_name}' has no executor")
912
+ return tool(action, self)
913
+
870
914
  def __del__(self) -> None:
871
915
  """Ensure cleanup happens when conversation is destroyed."""
872
916
  try:
@@ -6,7 +6,8 @@ import threading
6
6
  import time
7
7
  import uuid
8
8
  from collections.abc import Mapping
9
- from typing import SupportsIndex, overload
9
+ from queue import Empty, Queue
10
+ from typing import TYPE_CHECKING, SupportsIndex, overload
10
11
  from urllib.parse import urlparse
11
12
 
12
13
  import httpx
@@ -14,6 +15,10 @@ import websockets
14
15
 
15
16
  from openhands.sdk.agent.base import AgentBase
16
17
  from openhands.sdk.conversation.base import BaseConversation, ConversationStateProtocol
18
+
19
+
20
+ if TYPE_CHECKING:
21
+ from openhands.sdk.tool.schema import Action, Observation
17
22
  from openhands.sdk.conversation.conversation_stats import ConversationStats
18
23
  from openhands.sdk.conversation.events_list_base import EventsListBase
19
24
  from openhands.sdk.conversation.exceptions import (
@@ -555,6 +560,7 @@ class RemoteConversation(BaseConversation):
555
560
  _client: httpx.Client
556
561
  _hook_processor: HookEventProcessor | None
557
562
  _cleanup_initiated: bool
563
+ _terminal_status_queue: Queue[str] # Thread-safe queue for terminal status from WS
558
564
  delete_on_close: bool = False
559
565
 
560
566
  def __init__(
@@ -609,6 +615,7 @@ class RemoteConversation(BaseConversation):
609
615
  self._client = workspace.client
610
616
  self._hook_processor = None
611
617
  self._cleanup_initiated = False
618
+ self._terminal_status_queue: Queue[str] = Queue()
612
619
 
613
620
  should_create = conversation_id is None
614
621
  if conversation_id is not None:
@@ -708,8 +715,21 @@ class RemoteConversation(BaseConversation):
708
715
  # No visualization (visualizer is None)
709
716
  self._visualizer = None
710
717
 
718
+ # Add a callback that signals when run completes via WebSocket
719
+ # This ensures we wait for all events to be delivered before run() returns
720
+ def run_complete_callback(event: Event) -> None:
721
+ if isinstance(event, ConversationStateUpdateEvent):
722
+ if event.key == "execution_status":
723
+ try:
724
+ status = ConversationExecutionStatus(event.value)
725
+ if status.is_terminal():
726
+ self._terminal_status_queue.put(event.value)
727
+ except ValueError:
728
+ pass # Unknown status value, ignore
729
+
711
730
  # Compose all callbacks into a single callback
712
- composed_callback = BaseConversation.compose_callbacks(self._callbacks)
731
+ all_callbacks = self._callbacks + [run_complete_callback]
732
+ composed_callback = BaseConversation.compose_callbacks(all_callbacks)
713
733
 
714
734
  # Initialize WebSocket client for callbacks
715
735
  self._ws_client = WebSocketCallbackClient(
@@ -862,6 +882,14 @@ class RemoteConversation(BaseConversation):
862
882
  Raises:
863
883
  ConversationRunError: If the run fails or times out.
864
884
  """
885
+ # Drain any stale terminal status events from previous runs.
886
+ # This prevents stale events from causing early returns.
887
+ while True:
888
+ try:
889
+ self._terminal_status_queue.get_nowait()
890
+ except Empty:
891
+ break
892
+
865
893
  # Trigger a run on the server using the dedicated run endpoint.
866
894
  # Let the server tell us if it's already running (409), avoiding an extra GET.
867
895
  try:
@@ -889,10 +917,20 @@ class RemoteConversation(BaseConversation):
889
917
  poll_interval: float = 1.0,
890
918
  timeout: float = 1800.0,
891
919
  ) -> None:
892
- """Poll the server until the conversation is no longer running.
920
+ """Wait for the conversation run to complete.
921
+
922
+ This method waits for the run to complete by listening for the terminal
923
+ status event via WebSocket. This ensures all events are delivered before
924
+ returning, avoiding the race condition where polling sees "finished"
925
+ status before WebSocket delivers the final events.
926
+
927
+ As a fallback, it also polls the server periodically. If the WebSocket
928
+ is delayed or disconnected, we return after multiple consecutive polls
929
+ show a terminal status, and reconcile events to catch any that were
930
+ missed via WebSocket.
893
931
 
894
932
  Args:
895
- poll_interval: Time in seconds between status polls.
933
+ poll_interval: Time in seconds between status polls (fallback).
896
934
  timeout: Maximum time in seconds to wait.
897
935
 
898
936
  Raises:
@@ -901,6 +939,14 @@ class RemoteConversation(BaseConversation):
901
939
  responses are retried until timeout.
902
940
  """
903
941
  start_time = time.monotonic()
942
+ consecutive_terminal_polls = 0
943
+ # Return after this many consecutive terminal polls (fallback for WS issues).
944
+ # We use 3 polls to balance latency vs reliability:
945
+ # - 1 poll could be a transient state during shutdown
946
+ # - 2 polls might still catch a race condition
947
+ # - 3 polls (with default 1s interval = 3s total) provides high confidence
948
+ # that the run is truly complete while keeping fallback latency reasonable
949
+ TERMINAL_POLL_THRESHOLD = 3
904
950
 
905
951
  while True:
906
952
  elapsed = time.monotonic() - start_time
@@ -913,20 +959,57 @@ class RemoteConversation(BaseConversation):
913
959
  ),
914
960
  )
915
961
 
962
+ # Wait for either:
963
+ # 1. WebSocket delivers terminal status event (preferred)
964
+ # 2. Poll interval expires (fallback - check status via REST)
965
+ try:
966
+ ws_status = self._terminal_status_queue.get(timeout=poll_interval)
967
+ # Handle ERROR/STUCK states - raises ConversationRunError
968
+ self._handle_conversation_status(ws_status)
969
+
970
+ logger.info(
971
+ "Run completed via WebSocket notification "
972
+ "(status: %s, elapsed: %.1fs)",
973
+ ws_status,
974
+ elapsed,
975
+ )
976
+ return
977
+ except Empty:
978
+ pass # Queue.get() timed out, fall through to REST polling
979
+
980
+ # Poll the server for status as a health check and fallback.
981
+ # This catches ERROR/STUCK states that need immediate attention,
982
+ # and provides a fallback if WebSocket is delayed/disconnected.
916
983
  try:
917
984
  status = self._poll_status_once()
918
985
  except Exception as exc:
919
986
  self._handle_poll_exception(exc)
987
+ consecutive_terminal_polls = 0 # Reset on error
920
988
  else:
921
- if self._handle_conversation_status(status):
922
- logger.info(
923
- "Run completed with status: %s (elapsed: %.1fs)",
924
- status,
925
- elapsed,
926
- )
927
- return
928
-
929
- time.sleep(poll_interval)
989
+ # Raises ConversationRunError for ERROR/STUCK states
990
+ self._handle_conversation_status(status)
991
+
992
+ # Track consecutive terminal polls as a fallback for WS issues.
993
+ # If WebSocket is delayed/disconnected, we return after multiple
994
+ # consecutive polls confirm the terminal status.
995
+ if status and ConversationExecutionStatus(status).is_terminal():
996
+ consecutive_terminal_polls += 1
997
+ if consecutive_terminal_polls >= TERMINAL_POLL_THRESHOLD:
998
+ logger.info(
999
+ "Run completed via REST fallback after %d consecutive "
1000
+ "terminal polls (status: %s, elapsed: %.1fs). "
1001
+ "Reconciling events...",
1002
+ consecutive_terminal_polls,
1003
+ status,
1004
+ elapsed,
1005
+ )
1006
+ # Reconcile events to catch any that were missed via WS.
1007
+ # This is only called in the fallback path, so it doesn't
1008
+ # add overhead in the common case where WS works.
1009
+ self._state.events.reconcile()
1010
+ return
1011
+ else:
1012
+ consecutive_terminal_polls = 0
930
1013
 
931
1014
  def _poll_status_once(self) -> str | None:
932
1015
  """Fetch the current execution status from the remote conversation."""
@@ -1116,6 +1199,28 @@ class RemoteConversation(BaseConversation):
1116
1199
  """
1117
1200
  _send_request(self._client, "POST", f"/api/conversations/{self._id}/condense")
1118
1201
 
1202
+ def execute_tool(self, tool_name: str, action: "Action") -> "Observation":
1203
+ """Execute a tool directly without going through the agent loop.
1204
+
1205
+ Note: This method is not yet supported for RemoteConversation.
1206
+ Tool execution for remote conversations happens on the server side
1207
+ during the normal agent loop.
1208
+
1209
+ Args:
1210
+ tool_name: The name of the tool to execute
1211
+ action: The action to pass to the tool executor
1212
+
1213
+ Raises:
1214
+ NotImplementedError: Always, as this feature is not yet supported
1215
+ for remote conversations.
1216
+ """
1217
+ raise NotImplementedError(
1218
+ "execute_tool is not yet supported for RemoteConversation. "
1219
+ "Tool execution for remote conversations happens on the server side "
1220
+ "during the normal agent loop. Use LocalConversation for direct "
1221
+ "tool execution."
1222
+ )
1223
+
1119
1224
  def close(self) -> None:
1120
1225
  """Close the conversation and clean up resources.
1121
1226
 
@@ -45,6 +45,25 @@ class ConversationExecutionStatus(str, Enum):
45
45
  STUCK = "stuck" # Conversation is stuck in a loop or unable to proceed
46
46
  DELETING = "deleting" # Conversation is in the process of being deleted
47
47
 
48
+ def is_terminal(self) -> bool:
49
+ """Check if this status represents a terminal state.
50
+
51
+ Terminal states indicate the run has completed and the agent is no longer
52
+ actively processing. These are: FINISHED, ERROR, STUCK.
53
+
54
+ Note: IDLE is NOT a terminal state - it's the initial state of a conversation
55
+ before any run has started. Including IDLE would cause false positives when
56
+ the WebSocket delivers the initial state update during connection.
57
+
58
+ Returns:
59
+ True if this is a terminal status, False otherwise.
60
+ """
61
+ return self in (
62
+ ConversationExecutionStatus.FINISHED,
63
+ ConversationExecutionStatus.ERROR,
64
+ ConversationExecutionStatus.STUCK,
65
+ )
66
+
48
67
 
49
68
  class ConversationState(OpenHandsModel):
50
69
  # ===== Public, validated fields =====
@@ -170,21 +170,12 @@ class TextContent(BaseContent):
170
170
  model_config: ClassVar[ConfigDict] = ConfigDict(
171
171
  extra="forbid", populate_by_name=True
172
172
  )
173
- enable_truncation: bool = True
174
173
 
175
174
  def to_llm_dict(self) -> list[dict[str, str | dict[str, str]]]:
176
175
  """Convert to LLM API format."""
177
- text = self.text
178
- if self.enable_truncation and len(text) > DEFAULT_TEXT_CONTENT_LIMIT:
179
- logger.warning(
180
- f"TextContent text length ({len(text)}) exceeds limit "
181
- f"({DEFAULT_TEXT_CONTENT_LIMIT}), truncating"
182
- )
183
- text = maybe_truncate(text, DEFAULT_TEXT_CONTENT_LIMIT)
184
-
185
176
  data: dict[str, str | dict[str, str]] = {
186
177
  "type": self.type,
187
- "text": text,
178
+ "text": self.text,
188
179
  }
189
180
  if self.cache_prompt:
190
181
  data["cache_control"] = {"type": "ephemeral"}
@@ -342,6 +333,8 @@ class Message(BaseModel):
342
333
  content = "\n".join(
343
334
  item.text for item in self.content if isinstance(item, TextContent)
344
335
  )
336
+ if self.role == "tool":
337
+ content = self._maybe_truncate_tool_text(content)
345
338
  message_dict: dict[str, Any] = {"content": content, "role": self.role}
346
339
 
347
340
  # tool call keys are added in to_chat_dict to centralize behavior
@@ -366,6 +359,12 @@ class Message(BaseModel):
366
359
  # All content types now return list[dict[str, Any]]
367
360
  item_dicts = item.to_llm_dict()
368
361
 
362
+ if self.role == "tool" and item_dicts:
363
+ for d in item_dicts:
364
+ text_val = d.get("text")
365
+ if d.get("type") == "text" and isinstance(text_val, str):
366
+ d["text"] = self._maybe_truncate_tool_text(text_val)
367
+
369
368
  # We have to remove cache_prompt for tool content and move it up to the
370
369
  # message level
371
370
  # See discussion here for details: https://github.com/BerriAI/litellm/issues/6422#issuecomment-2438765472
@@ -551,17 +550,28 @@ class Message(BaseModel):
551
550
  )
552
551
  for c in self.content:
553
552
  if isinstance(c, TextContent):
553
+ output_text = self._maybe_truncate_tool_text(c.text)
554
554
  items.append(
555
555
  {
556
556
  "type": "function_call_output",
557
557
  "call_id": resp_call_id,
558
- "output": c.text,
558
+ "output": output_text,
559
559
  }
560
560
  )
561
561
  return items
562
562
 
563
563
  return items
564
564
 
565
+ def _maybe_truncate_tool_text(self, text: str) -> str:
566
+ if not text or len(text) <= DEFAULT_TEXT_CONTENT_LIMIT:
567
+ return text
568
+ logger.warning(
569
+ "Tool TextContent text length (%s) exceeds limit (%s), truncating",
570
+ len(text),
571
+ DEFAULT_TEXT_CONTENT_LIMIT,
572
+ )
573
+ return maybe_truncate(text, DEFAULT_TEXT_CONTENT_LIMIT)
574
+
565
575
  @classmethod
566
576
  def from_llm_chat_message(cls, message: LiteLLMMessage) -> "Message":
567
577
  """Convert a LiteLLMMessage (Chat Completions) to our Message class.
@@ -1,5 +1,6 @@
1
1
  VERIFIED_OPENAI_MODELS = [
2
2
  "gpt-5.2",
3
+ "gpt-5.2-codex",
3
4
  "gpt-5.1",
4
5
  "gpt-5.1-codex-max",
5
6
  "gpt-5.1-codex",
@@ -46,12 +47,14 @@ VERIFIED_OPENHANDS_MODELS = [
46
47
  "claude-opus-4-5-20251101",
47
48
  "claude-sonnet-4-5-20250929",
48
49
  "gpt-5.2",
50
+ "gpt-5.2-codex",
49
51
  "gpt-5.1-codex-max",
50
52
  "gpt-5.1-codex",
51
53
  "gpt-5.1",
52
54
  "gemini-3-pro-preview",
53
55
  "deepseek-chat",
54
56
  "kimi-k2-thinking",
57
+ "kimi-k2.5",
55
58
  "devstral-medium-2512",
56
59
  "devstral-2512",
57
60
  ]
openhands/sdk/mcp/tool.py CHANGED
@@ -29,6 +29,9 @@ from openhands.sdk.utils.models import DiscriminatedUnionMixin
29
29
 
30
30
  logger = get_logger(__name__)
31
31
 
32
+ # Default timeout for MCP tool execution in seconds
33
+ MCP_TOOL_TIMEOUT_SECONDS = 300
34
+
32
35
 
33
36
  # NOTE: We don't define MCPToolAction because it
34
37
  # will be a pydantic BaseModel dynamically created from the MCP tool schema.
@@ -45,10 +48,17 @@ class MCPToolExecutor(ToolExecutor):
45
48
 
46
49
  tool_name: str
47
50
  client: MCPClient
51
+ timeout: float
48
52
 
49
- def __init__(self, tool_name: str, client: MCPClient):
53
+ def __init__(
54
+ self,
55
+ tool_name: str,
56
+ client: MCPClient,
57
+ timeout: float = MCP_TOOL_TIMEOUT_SECONDS,
58
+ ):
50
59
  self.tool_name = tool_name
51
60
  self.client = client
61
+ self.timeout = timeout
52
62
 
53
63
  @observe(name="MCPToolExecutor.call_tool", span_type="TOOL")
54
64
  async def call_tool(self, action: MCPToolAction) -> MCPToolObservation:
@@ -83,9 +93,22 @@ class MCPToolExecutor(ToolExecutor):
83
93
  conversation: "LocalConversation | None" = None, # noqa: ARG002
84
94
  ) -> MCPToolObservation:
85
95
  """Execute an MCP tool call."""
86
- return self.client.call_async_from_sync(
87
- self.call_tool, action=action, timeout=300
88
- )
96
+ try:
97
+ return self.client.call_async_from_sync(
98
+ self.call_tool, action=action, timeout=self.timeout
99
+ )
100
+ except TimeoutError:
101
+ error_msg = (
102
+ f"MCP tool '{self.tool_name}' timed out after {self.timeout} seconds. "
103
+ "The tool server may be unresponsive or the operation is taking "
104
+ "too long. Consider retrying or using an alternative approach."
105
+ )
106
+ logger.error(error_msg)
107
+ return MCPToolObservation.from_text(
108
+ text=error_msg,
109
+ is_error=True,
110
+ tool_name=self.tool_name,
111
+ )
89
112
 
90
113
 
91
114
  _mcp_dynamic_action_type: dict[str, type[Schema]] = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.11.0
3
+ Version: 1.11.1
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
@@ -35,12 +35,12 @@ openhands/sdk/context/prompts/templates/skill_knowledge_info.j2,sha256=boqEsAIsz
35
35
  openhands/sdk/context/prompts/templates/system_message_suffix.j2,sha256=MJYiCSm8gq--6HKt74baDTarexbG4vQdo6Ba1nvjSHA,2911
36
36
  openhands/sdk/context/skills/__init__.py,sha256=dS6XJYLHlm_wboD-C42bRw4HCFu-o9dzCiFRpoyR1h0,938
37
37
  openhands/sdk/context/skills/exceptions.py,sha256=tVBSbXTXG32nb1TebVAuzbNNHvn8GScpSM1bHCnQzvY,305
38
- openhands/sdk/context/skills/skill.py,sha256=rSXEW8VN0Fw4SDI1PU6JY3MiCT3EIv9AR2DUumLBguw,35348
38
+ openhands/sdk/context/skills/skill.py,sha256=GMj_OZNi6KSIRluswO_mZXAJhXGdGpYPZthj4WZf92Q,35760
39
39
  openhands/sdk/context/skills/trigger.py,sha256=ZGaDmMpJghnAEuTTYX6UepsA5nX1CSz83zK1Ox46vMk,756
40
40
  openhands/sdk/context/skills/types.py,sha256=LvyCveHBSt2-g9Lbpr_eQMvOd4eEBjJb3irAWL-OzE0,1813
41
41
  openhands/sdk/context/skills/utils.py,sha256=kpJjVz_BQGjFrgO8QpBwTAqHbAU8spD6crOLI_ollac,11870
42
42
  openhands/sdk/conversation/__init__.py,sha256=USxX0PTUI9ufA8CJ8iyDkIaKR5iFbDo3L9G5tBx3k94,1509
43
- openhands/sdk/conversation/base.py,sha256=btRjqHk4FkAJjIAi34fgkNUml9R-0qfsDsJgomlN-kI,9143
43
+ openhands/sdk/conversation/base.py,sha256=SzqLFHRgQPLzR3jiAD7Cc-ZLCJnNQ1Zwu1yXyHK6eKA,10426
44
44
  openhands/sdk/conversation/conversation.py,sha256=PPoR13vFo_9DeIyeW82cxGonDpoOvKjSsQi5OrqR4Zk,6660
45
45
  openhands/sdk/conversation/conversation_stats.py,sha256=ZlQ99kgG5YVCrZ4rqJlq63JaiInxX8jqv-q5lS7RN68,3038
46
46
  openhands/sdk/conversation/event_store.py,sha256=JZF6AibFezcIzEw4IQqKHG8T8s53SfD_LkZycsOH6xY,7992
@@ -51,13 +51,13 @@ openhands/sdk/conversation/persistence_const.py,sha256=om3pOQa5sGK8t_NUYb3Tz-7sK
51
51
  openhands/sdk/conversation/response_utils.py,sha256=rPlC3cDSmoQte6NZ0kK6h6-9ho5cbF8jEw-DiyEhgIM,1548
52
52
  openhands/sdk/conversation/secret_registry.py,sha256=6fY1zRxb55rC4uIMFcR0lDssIyyjaPh9pCWGqDikrek,4446
53
53
  openhands/sdk/conversation/serialization_diff.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- openhands/sdk/conversation/state.py,sha256=VF3PD2THqgci6mA-meMaNGpB1KuCEW_4AOtHEIQ0flQ,17557
54
+ openhands/sdk/conversation/state.py,sha256=_Bg2lTyZj3JtFGqMPOlHxz2yBcu-XRmhuHanWhfzi3A,18320
55
55
  openhands/sdk/conversation/stuck_detector.py,sha256=p8DljC-YwAVpAUTUqcLy2CY1hTSdpKzN9f6iKJbxs8I,12185
56
56
  openhands/sdk/conversation/title_utils.py,sha256=j40-dP-Oes-mhU2xUC7fCC8cB0wkMdbbDJU7WLHiVIo,7063
57
57
  openhands/sdk/conversation/types.py,sha256=q_yc3VNc8r3cdmvPXzpj7HvdLeDqv-37hCgOWMU65a4,1507
58
58
  openhands/sdk/conversation/impl/__init__.py,sha256=DmDFyNR4RU8eiMocKf2j9eBQomipP-rrJgU1LoVWTDA,220
59
- openhands/sdk/conversation/impl/local_conversation.py,sha256=p7ETqzDhwxdW076TCOKHcxZuEDP7NrbERkSJSG2tZZg,37183
60
- openhands/sdk/conversation/impl/remote_conversation.py,sha256=g5cQu0WCIWHjAJz3Zp0cMtItVCd81mfLpPgvnbLdyrU,43960
59
+ openhands/sdk/conversation/impl/local_conversation.py,sha256=gb5-_MhrhEQIHGKVGvgodaGRQ2mi1YI_vsy6f30EkE4,39031
60
+ openhands/sdk/conversation/impl/remote_conversation.py,sha256=ocKqRXevb0z9g0AhKGlI6dkIAPFx57tj28ciESSZueE,49305
61
61
  openhands/sdk/conversation/visualizer/__init__.py,sha256=0LXpKlt2eJcrqP1z6jQP_nLx23V8ErnQkKYSxvUp0_A,275
62
62
  openhands/sdk/conversation/visualizer/base.py,sha256=oMg-JvQc34ebYdC3J9itHraoB2u3MdQ6E77AKiTmu30,3198
63
63
  openhands/sdk/conversation/visualizer/default.py,sha256=k-3-l1j8H_EOEn_pfzsUczcc4JDhQni7AUQZgZ2TpB4,11885
@@ -108,7 +108,7 @@ openhands/sdk/llm/__init__.py,sha256=t7kEGnNq4eYREz5h2n1Pizte8glUnruHTt_G_6DguYk
108
108
  openhands/sdk/llm/llm.py,sha256=rQpiHy-clgGqN9fms4RaSIkJ7nV5HdXvgMCq95di89Y,52102
109
109
  openhands/sdk/llm/llm_registry.py,sha256=DL9yqSbAM7OBkzdIChLuxG2qk_oElW2tC2xem6mq0F8,3530
110
110
  openhands/sdk/llm/llm_response.py,sha256=DaBVBkij4Sz-RsYhRb3UUcvJCTzCBcOYQ9IhFwN4ukI,1988
111
- openhands/sdk/llm/message.py,sha256=YK--c42j6Pb7wjUrPeIfiRRIt0w0pmJMHvGIZC3ugO4,27085
111
+ openhands/sdk/llm/message.py,sha256=pO-uuGByNQb6mPlnkiYo2TNvIp3l2mgDWxsi6CQ-C7k,27561
112
112
  openhands/sdk/llm/streaming.py,sha256=tFJ7B0AjJ-e8Xv13DTtc2FdrsLRUCG8wxQex8fDlOp4,214
113
113
  openhands/sdk/llm/auth/__init__.py,sha256=GDGU9D6a-o6bXSlVC8NUMxViXJfmEr55HMRosog9F_k,698
114
114
  openhands/sdk/llm/auth/credentials.py,sha256=uPWqBCd26snW5Afuinzxy6I-HlG38qXZ9o_hD5iKc64,5167
@@ -134,7 +134,7 @@ openhands/sdk/llm/utils/model_prompt_spec.py,sha256=onw9-y7x0aJS8IOjNzeqhdvcFNwK
134
134
  openhands/sdk/llm/utils/retry_mixin.py,sha256=M-hXp8EwP1FjNN6tgHiv133BtUQgRr9Kz_ZWxeAJLGA,4765
135
135
  openhands/sdk/llm/utils/telemetry.py,sha256=E7fDPXFdy3u3IVPTOijUCDw8vtqtMPjyhhlIg-NwU-M,15533
136
136
  openhands/sdk/llm/utils/unverified_models.py,sha256=SmYrX_WxXOJBanTviztqy1xPjOcLY4i3qvwNBEga_Dk,4797
137
- openhands/sdk/llm/utils/verified_models.py,sha256=_ioY2NHay8MQmfeiWRkT_53pA7DFhldopfQotTwORbk,1462
137
+ openhands/sdk/llm/utils/verified_models.py,sha256=tjIhYM8Bq621ewlmxMztHXtrk98jk98u-TDloWUE3xI,1521
138
138
  openhands/sdk/logger/__init__.py,sha256=vZvFDYfW01Y8Act3tveMs3XxTysJlt4HeT-n6X_ujYk,330
139
139
  openhands/sdk/logger/logger.py,sha256=en8SHzFC2baohQmqbgE8t0mvV_xMZQrILEycOzdAZL0,6511
140
140
  openhands/sdk/logger/rolling.py,sha256=E6oy0asgmOhZHoWlSCw0QK1PKnS6kvtxjoWLAsqlGvs,3440
@@ -142,7 +142,7 @@ openhands/sdk/mcp/__init__.py,sha256=-wQbZ405PjVRCBtSfirp4jsiRohd7IJAyAdicZ-M8Ok
142
142
  openhands/sdk/mcp/client.py,sha256=NTDTYTxedRDB0xkkMwT1_uN2ReV4SvLsURKNB8Ih0lg,3754
143
143
  openhands/sdk/mcp/definition.py,sha256=vFLQeLW99fBzPGR7X7_1GzTmIHlSVAbmsg3elhhkp5U,3424
144
144
  openhands/sdk/mcp/exceptions.py,sha256=N4g7Wju420TQJ7hmck1e5UblWbC_7Torb-UTFj1GN70,448
145
- openhands/sdk/mcp/tool.py,sha256=c7Pzgm_n5iAT4TK4zAJsdiaLkvouzuHjdBK2N4R9N6M,10380
145
+ openhands/sdk/mcp/tool.py,sha256=33jaPGmehCf6AT88e9CFXTlmP7JNups-yIMxlbLvIFo,11153
146
146
  openhands/sdk/mcp/utils.py,sha256=Drm3D1SuNknwNa9yfqIVRzZhEXlqHj39LhLz0XbEF04,3081
147
147
  openhands/sdk/observability/__init__.py,sha256=JKHDWoj01igaCUChdXgKmstESiMmbAK9CN5XzT2H7fo,122
148
148
  openhands/sdk/observability/laminar.py,sha256=U2AbSWpPcUYdrzx__-BEg4LPW13f1l-Hk-V4qeBmE3k,5053
@@ -190,7 +190,7 @@ openhands/sdk/workspace/remote/__init__.py,sha256=eKkj6NOESMUBGDVC6_L2Wfuc4K6G-m
190
190
  openhands/sdk/workspace/remote/async_remote_workspace.py,sha256=MfnYoXvx_tZ7MKDGJCofnkYAJxfBKqNtM2Qprx3QQRk,5608
191
191
  openhands/sdk/workspace/remote/base.py,sha256=5xqhJf_Hi3kMdNfL4u0XScnVShTRUSeMtvIAemR6Q3A,6317
192
192
  openhands/sdk/workspace/remote/remote_workspace_mixin.py,sha256=29Mwe6lVZ-annN0lThRY2cM-FrqXhLm1uT_4j72e7fw,12600
193
- openhands_sdk-1.11.0.dist-info/METADATA,sha256=L9sD54xo2JltGA6PFXQtMuJRpOZkTxPpoTH4HRa3u44,859
194
- openhands_sdk-1.11.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
195
- openhands_sdk-1.11.0.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
196
- openhands_sdk-1.11.0.dist-info/RECORD,,
193
+ openhands_sdk-1.11.1.dist-info/METADATA,sha256=-s87W5UCYLj1oVPQ7kNSdQtNv5YUZH5Lre2Y-iEMkmU,859
194
+ openhands_sdk-1.11.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
195
+ openhands_sdk-1.11.1.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
196
+ openhands_sdk-1.11.1.dist-info/RECORD,,