openhands-sdk 1.4.1__py3-none-any.whl → 1.5.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.
@@ -186,7 +186,7 @@ class Agent(AgentBase):
186
186
  )
187
187
  on_event(error_message)
188
188
  return
189
- except LLMContextWindowExceedError:
189
+ except LLMContextWindowExceedError as e:
190
190
  # If condenser is available and handles requests, trigger condensation
191
191
  if (
192
192
  self.condenser is not None
@@ -197,8 +197,9 @@ class Agent(AgentBase):
197
197
  )
198
198
  on_event(CondensationRequest())
199
199
  return
200
- # No condenser available; re-raise for client handling
201
- raise
200
+ # No condenser available or doesn't handle requests; log helpful warning
201
+ self._log_context_window_exceeded_warning()
202
+ raise e
202
203
 
203
204
  # LLMResponse already contains the converted message and metrics snapshot
204
205
  message: Message = llm_response.message
@@ -516,3 +517,98 @@ class Agent(AgentBase):
516
517
  ]["token_ids"],
517
518
  )
518
519
  on_event(token_event)
520
+
521
+ def _log_context_window_exceeded_warning(self) -> None:
522
+ """Log a helpful warning when context window is exceeded without a condenser."""
523
+ if self.condenser is None:
524
+ logger.warning(
525
+ "\n"
526
+ "=" * 80 + "\n"
527
+ "⚠️ CONTEXT WINDOW EXCEEDED ERROR\n"
528
+ "=" * 80 + "\n"
529
+ "\n"
530
+ "The LLM's context window has been exceeded, but no condenser is "
531
+ "configured.\n"
532
+ "\n"
533
+ "Current configuration:\n"
534
+ f" • Condenser: None\n"
535
+ f" • LLM Model: {self.llm.model}\n"
536
+ "\n"
537
+ "To prevent this error, configure a condenser to automatically "
538
+ "summarize\n"
539
+ "conversation history when it gets too long.\n"
540
+ "\n"
541
+ "Example configuration:\n"
542
+ "\n"
543
+ " from openhands.sdk import Agent, LLM\n"
544
+ " from openhands.sdk.context.condenser import "
545
+ "LLMSummarizingCondenser\n"
546
+ "\n"
547
+ " agent = Agent(\n"
548
+ " llm=LLM(model='your-model'),\n"
549
+ " condenser=LLMSummarizingCondenser(\n"
550
+ " llm=LLM(model='your-model'), # Can use same or "
551
+ "cheaper model\n"
552
+ " max_size=120, # Maximum events before condensation\n"
553
+ " keep_first=4 # Number of initial events to preserve\n"
554
+ " )\n"
555
+ " )\n"
556
+ "\n"
557
+ "For more information, see: "
558
+ "https://docs.openhands.dev/sdk/guides/context-condenser\n"
559
+ "=" * 80
560
+ )
561
+ else:
562
+ condenser_type = type(self.condenser).__name__
563
+ handles_requests = self.condenser.handles_condensation_requests()
564
+ condenser_config = self.condenser.model_dump(
565
+ exclude={"llm"}, exclude_none=True
566
+ )
567
+ condenser_llm_obj = getattr(self.condenser, "llm", None)
568
+ condenser_llm = (
569
+ condenser_llm_obj.model if condenser_llm_obj is not None else "N/A"
570
+ )
571
+
572
+ logger.warning(
573
+ "\n"
574
+ "=" * 80 + "\n"
575
+ "⚠️ CONTEXT WINDOW EXCEEDED ERROR\n"
576
+ "=" * 80 + "\n"
577
+ "\n"
578
+ "The LLM's context window has been exceeded.\n"
579
+ "\n"
580
+ "Current configuration:\n"
581
+ f" • Condenser Type: {condenser_type}\n"
582
+ f" • Handles Condensation Requests: {handles_requests}\n"
583
+ f" • Condenser LLM: {condenser_llm}\n"
584
+ f" • Agent LLM Model: {self.llm.model}\n"
585
+ f" • Condenser Config: {json.dumps(condenser_config, indent=4)}\n"
586
+ "\n"
587
+ "Your condenser is configured but does not handle condensation "
588
+ "requests\n"
589
+ "(handles_condensation_requests() returned False).\n"
590
+ "\n"
591
+ "To fix this:\n"
592
+ " 1. Use LLMSummarizingCondenser which handles condensation "
593
+ "requests, OR\n"
594
+ " 2. Implement handles_condensation_requests() in your custom "
595
+ "condenser\n"
596
+ "\n"
597
+ "Example with LLMSummarizingCondenser:\n"
598
+ "\n"
599
+ " from openhands.sdk.context.condenser import "
600
+ "LLMSummarizingCondenser\n"
601
+ "\n"
602
+ " agent = Agent(\n"
603
+ " llm=LLM(model='your-model'),\n"
604
+ " condenser=LLMSummarizingCondenser(\n"
605
+ " llm=LLM(model='your-model'),\n"
606
+ " max_size=120,\n"
607
+ " keep_first=4\n"
608
+ " )\n"
609
+ " )\n"
610
+ "\n"
611
+ "For more information, see: "
612
+ "https://docs.openhands.dev/sdk/guides/context-condenser\n"
613
+ "=" * 80
614
+ )
@@ -292,6 +292,12 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
292
292
  )
293
293
  updates["condenser"] = new_condenser
294
294
 
295
+ # Reconcile agent_context - always use the current environment's agent_context
296
+ # This allows resuming conversations from different directories and handles
297
+ # cases where skills, working directory, or other context has changed
298
+ if self.agent_context is not None:
299
+ updates["agent_context"] = self.agent_context
300
+
295
301
  # Create maps by tool name for easy lookup
296
302
  runtime_tools_map = {tool.name: tool for tool in self.tools}
297
303
  persisted_tools_map = {tool.name: tool for tool in persisted.tools}
@@ -52,6 +52,7 @@ You are OpenHands agent, a helpful AI assistant that can interact with a compute
52
52
  * For bug fixes: Create tests to verify issues before implementing fixes
53
53
  * For new features: Consider test-driven development when appropriate
54
54
  * Do NOT write tests for documentation changes, README updates, configuration files, or other non-functionality changes
55
+ * Do not use mocks in tests unless strictly necessary and justify their use when they are used. You must always test real code paths in tests, NOT mocks.
55
56
  * If the repository lacks testing infrastructure and implementing tests would require extensive setup, consult with the user before investing time in building testing infrastructure
56
57
  * If the environment is not set up to run tests, consult with the user first before investing time to install all dependencies
57
58
  4. IMPLEMENTATION:
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import pathlib
4
+ from collections.abc import Mapping
2
5
 
3
6
  from pydantic import BaseModel, Field, field_validator, model_validator
4
7
 
@@ -11,6 +14,7 @@ from openhands.sdk.context.skills import (
11
14
  )
12
15
  from openhands.sdk.llm import Message, TextContent
13
16
  from openhands.sdk.logger import get_logger
17
+ from openhands.sdk.secret import SecretValue
14
18
 
15
19
 
16
20
  logger = get_logger(__name__)
@@ -65,6 +69,15 @@ class AgentContext(BaseModel):
65
69
  "This allows you to get the latest skills without SDK updates."
66
70
  ),
67
71
  )
72
+ secrets: Mapping[str, SecretValue] | None = Field(
73
+ default=None,
74
+ description=(
75
+ "Dictionary mapping secret keys to values or secret sources. "
76
+ "Secrets are used for authentication and sensitive data handling. "
77
+ "Values can be either strings or SecretSource instances "
78
+ "(str | SecretSource)."
79
+ ),
80
+ )
68
81
 
69
82
  @field_validator("skills")
70
83
  @classmethod
@@ -123,6 +136,16 @@ class AgentContext(BaseModel):
123
136
  logger.warning(f"Failed to load public skills: {str(e)}")
124
137
  return self
125
138
 
139
+ def get_secret_names(self) -> list[str]:
140
+ """Get the list of secret names from the secrets field.
141
+
142
+ Returns:
143
+ List of secret names. Returns an empty list if no secrets are configured.
144
+ """
145
+ if not self.secrets:
146
+ return []
147
+ return list(self.secrets.keys())
148
+
126
149
  def get_system_message_suffix(self) -> str | None:
127
150
  """Get the system message with repo skill content and custom suffix.
128
151
 
@@ -135,13 +158,15 @@ class AgentContext(BaseModel):
135
158
  repo_skills = [s for s in self.skills if s.trigger is None]
136
159
  logger.debug(f"Triggered {len(repo_skills)} repository skills: {repo_skills}")
137
160
  # Build the workspace context information
138
- if repo_skills:
161
+ secret_names = self.get_secret_names()
162
+ if repo_skills or self.system_message_suffix or secret_names:
139
163
  # TODO(test): add a test for this rendering to make sure they work
140
164
  formatted_text = render_template(
141
165
  prompt_dir=str(PROMPT_DIR),
142
166
  template_name="system_message_suffix.j2",
143
167
  repo_skills=repo_skills,
144
168
  system_message_suffix=self.system_message_suffix or "",
169
+ secret_names=secret_names,
145
170
  ).strip()
146
171
  return formatted_text
147
172
  elif self.system_message_suffix and self.system_message_suffix.strip():
@@ -14,3 +14,19 @@ Please follow them while working.
14
14
 
15
15
  {{ system_message_suffix }}
16
16
  {% endif %}
17
+ {% if secret_names %}
18
+ <CUSTOM_SECRETS>
19
+ ### Credential Access
20
+ * Automatic secret injection: When you reference a registered secret key in your bash command, the secret value will be automatically exported as an environment variable before your command executes.
21
+ * How to use secrets: Simply reference the secret key in your command (e.g., `echo ${GITHUB_TOKEN:0:8}` or `curl -H "Authorization: Bearer $API_KEY" https://api.example.com`). The system will detect the key name in your command text and export it as environment variable before it executes your command.
22
+ * Secret detection: The system performs case-insensitive matching to find secret keys in your command text. If a registered secret key appears anywhere in your command, its value will be made available as an environment variable.
23
+ * Security: Secret values are automatically masked in command output to prevent accidental exposure. You will see `<secret-hidden>` instead of the actual secret value in the output.
24
+ * Refreshing expired secrets: Some secrets (like GITHUB_TOKEN) may be updated periodically or expire over time. If a secret stops working (e.g., authentication failures), try using it again in a new command - the system should automatically use the refreshed value. For example, if GITHUB_TOKEN was used in a git remote URL and later expired, you can update the remote URL with the current token: `git remote set-url origin https://${GITHUB_TOKEN}@github.com/username/repo.git` to pick up the refreshed token value.
25
+ * If it still fails, report it to the user.
26
+
27
+ You have access to the following environment variables
28
+ {% for secret_name in secret_names %}
29
+ * **${{ secret_name }}**
30
+ {% endfor %}
31
+ </CUSTOM_SECRETS>
32
+ {% endif %}
@@ -245,6 +245,23 @@ class BaseConversation(ABC):
245
245
  """
246
246
  ...
247
247
 
248
+ @abstractmethod
249
+ def condense(self) -> None:
250
+ """Force condensation of the conversation history.
251
+
252
+ This method uses the existing condensation request pattern to trigger
253
+ condensation. It adds a CondensationRequest event to the conversation
254
+ and forces the agent to take a single step to process it.
255
+
256
+ The condensation will be applied immediately and will modify the conversation
257
+ state by adding a condensation event to the history.
258
+
259
+ Raises:
260
+ ValueError: If no condenser is configured or the condenser doesn't
261
+ handle condensation requests.
262
+ """
263
+ ...
264
+
248
265
  @staticmethod
249
266
  def compose_callbacks(callbacks: Iterable[CallbackType]) -> CallbackType:
250
267
  """Compose multiple callbacks into a single callback function.
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Self, overload
3
3
 
4
4
  from openhands.sdk.agent.base import AgentBase
5
5
  from openhands.sdk.conversation.base import BaseConversation
6
- from openhands.sdk.conversation.secret_registry import SecretValue
7
6
  from openhands.sdk.conversation.types import (
8
7
  ConversationCallbackType,
9
8
  ConversationID,
@@ -14,6 +13,7 @@ from openhands.sdk.conversation.visualizer import (
14
13
  DefaultConversationVisualizer,
15
14
  )
16
15
  from openhands.sdk.logger import get_logger
16
+ from openhands.sdk.secret import SecretValue
17
17
  from openhands.sdk.workspace import LocalWorkspace, RemoteWorkspace
18
18
 
19
19
 
@@ -24,6 +24,7 @@ from openhands.sdk.conversation.visualizer import (
24
24
  DefaultConversationVisualizer,
25
25
  )
26
26
  from openhands.sdk.event import (
27
+ CondensationRequest,
27
28
  MessageEvent,
28
29
  PauseEvent,
29
30
  UserRejectObservation,
@@ -540,6 +541,55 @@ class LocalConversation(BaseConversation):
540
541
  events=self._state.events, llm=llm_to_use, max_length=max_length
541
542
  )
542
543
 
544
+ def condense(self) -> None:
545
+ """Synchronously force condense the conversation history.
546
+
547
+ If the agent is currently running, `condense()` will wait for the
548
+ ongoing step to finish before proceeding.
549
+
550
+ Raises ValueError if no compatible condenser exists.
551
+ """
552
+
553
+ # Check if condenser is configured and handles condensation requests
554
+ if (
555
+ self.agent.condenser is None
556
+ or not self.agent.condenser.handles_condensation_requests()
557
+ ):
558
+ condenser_info = (
559
+ "No condenser configured"
560
+ if self.agent.condenser is None
561
+ else (
562
+ f"Condenser {type(self.agent.condenser).__name__} does not handle "
563
+ "condensation requests"
564
+ )
565
+ )
566
+ raise ValueError(
567
+ f"Cannot condense conversation: {condenser_info}. "
568
+ "To enable manual condensation, configure an "
569
+ "LLMSummarizingCondenser:\n\n"
570
+ "from openhands.sdk.context.condenser import LLMSummarizingCondenser\n"
571
+ "agent = Agent(\n"
572
+ " llm=your_llm,\n"
573
+ " condenser=LLMSummarizingCondenser(\n"
574
+ " llm=your_llm,\n"
575
+ " max_size=120,\n"
576
+ " keep_first=4\n"
577
+ " )\n"
578
+ ")"
579
+ )
580
+
581
+ # Add a condensation request event
582
+ condensation_request = CondensationRequest()
583
+ self._on_event(condensation_request)
584
+
585
+ # Force the agent to take a single step to process the condensation request
586
+ # This will trigger the condenser if it handles condensation requests
587
+ with self._state:
588
+ # Take a single step to process the condensation request
589
+ self.agent.step(self, on_event=self._on_event, on_token=self._on_token)
590
+
591
+ logger.info("Condensation request processed")
592
+
543
593
  def __del__(self) -> None:
544
594
  """Ensure cleanup happens when conversation is destroyed."""
545
595
  try:
@@ -746,6 +746,21 @@ class RemoteConversation(BaseConversation):
746
746
  data = resp.json()
747
747
  return data["title"]
748
748
 
749
+ def condense(self) -> None:
750
+ """Force condensation of the conversation history.
751
+
752
+ This method sends a condensation request to the remote agent server.
753
+ The server will use the existing condensation request pattern to trigger
754
+ condensation if a condenser is configured and handles condensation requests.
755
+
756
+ The condensation will be applied on the server side and will modify the
757
+ conversation state by adding a condensation event to the history.
758
+
759
+ Raises:
760
+ HTTPError: If the server returns an error (e.g., no condenser configured).
761
+ """
762
+ _send_request(self._client, "POST", f"/api/conversations/{self._id}/condense")
763
+
749
764
  def close(self) -> None:
750
765
  """Close the conversation and clean up resources.
751
766
 
@@ -4,18 +4,13 @@ from collections.abc import Mapping
4
4
 
5
5
  from pydantic import Field, PrivateAttr, SecretStr
6
6
 
7
- from openhands.sdk.conversation.secret_source import (
8
- SecretSource,
9
- StaticSecret,
10
- )
11
7
  from openhands.sdk.logger import get_logger
8
+ from openhands.sdk.secret import SecretSource, SecretValue, StaticSecret
12
9
  from openhands.sdk.utils.models import OpenHandsModel
13
10
 
14
11
 
15
12
  logger = get_logger(__name__)
16
13
 
17
- SecretValue = str | SecretSource
18
-
19
14
 
20
15
  class SecretRegistry(OpenHandsModel):
21
16
  """Manages secrets and injects them into bash commands when needed.
openhands/sdk/llm/llm.py CHANGED
@@ -260,7 +260,7 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
260
260
  "Requires verified OpenAI organization. Only sent when explicitly set.",
261
261
  )
262
262
  enable_encrypted_reasoning: bool = Field(
263
- default=False,
263
+ default=True,
264
264
  description="If True, ask for ['reasoning.encrypted_content'] "
265
265
  "in Responses API include.",
266
266
  )
@@ -315,7 +315,9 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
315
315
  # =========================================================================
316
316
  # Internal fields (excluded from dumps)
317
317
  # =========================================================================
318
- retry_listener: SkipJsonSchema[Callable[[int, int], None] | None] = Field(
318
+ retry_listener: SkipJsonSchema[
319
+ Callable[[int, int, BaseException | None], None] | None
320
+ ] = Field(
319
321
  default=None,
320
322
  exclude=True,
321
323
  )
@@ -369,8 +371,9 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
369
371
  if model_val.startswith("openhands/"):
370
372
  model_name = model_val.removeprefix("openhands/")
371
373
  d["model"] = f"litellm_proxy/{model_name}"
372
- # Set base_url (default to the app proxy when base_url is unset)
373
- d["base_url"] = d.get("base_url", "https://llm-proxy.app.all-hands.dev/")
374
+ # Set base_url (default to the app proxy when base_url is unset or None)
375
+ # Use `or` instead of dict.get() to handle explicit None values
376
+ d["base_url"] = d.get("base_url") or "https://llm-proxy.app.all-hands.dev/"
374
377
 
375
378
  # HF doesn't support the OpenAI default value for top_p (1)
376
379
  if model_val.startswith("huggingface"):
@@ -426,6 +429,14 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
426
429
  )
427
430
  return self
428
431
 
432
+ def _retry_listener_fn(
433
+ self, attempt_number: int, num_retries: int, _err: BaseException | None
434
+ ) -> None:
435
+ if self.retry_listener is not None:
436
+ self.retry_listener(attempt_number, num_retries, _err)
437
+ if self._telemetry is not None and _err is not None:
438
+ self._telemetry.on_error(_err)
439
+
429
440
  # =========================================================================
430
441
  # Serializers
431
442
  # =========================================================================
@@ -559,7 +570,7 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
559
570
  retry_min_wait=self.retry_min_wait,
560
571
  retry_max_wait=self.retry_max_wait,
561
572
  retry_multiplier=self.retry_multiplier,
562
- retry_listener=self.retry_listener,
573
+ retry_listener=self._retry_listener_fn,
563
574
  )
564
575
  def _one_attempt(**retry_kwargs) -> ModelResponse:
565
576
  assert self._telemetry is not None
@@ -671,7 +682,6 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
671
682
  "kwargs": {k: v for k, v in call_kwargs.items()},
672
683
  "context_window": self.max_input_tokens or 0,
673
684
  }
674
- self._telemetry.on_request(log_ctx=log_ctx)
675
685
 
676
686
  # Perform call with retries
677
687
  @self.retry_decorator(
@@ -680,9 +690,11 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
680
690
  retry_min_wait=self.retry_min_wait,
681
691
  retry_max_wait=self.retry_max_wait,
682
692
  retry_multiplier=self.retry_multiplier,
683
- retry_listener=self.retry_listener,
693
+ retry_listener=self._retry_listener_fn,
684
694
  )
685
695
  def _one_attempt(**retry_kwargs) -> ResponsesAPIResponse:
696
+ assert self._telemetry is not None
697
+ self._telemetry.on_request(log_ctx=log_ctx)
686
698
  final_kwargs = {**call_kwargs, **retry_kwargs}
687
699
  with self._litellm_modify_params_ctx(self.modify_params):
688
700
  with warnings.catch_warnings():
@@ -713,7 +725,6 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
713
725
  f"Expected ResponsesAPIResponse, got {type(ret)}"
714
726
  )
715
727
  # telemetry (latency, cost). Token usage mapping we handle after.
716
- assert self._telemetry is not None
717
728
  self._telemetry.on_response(ret)
718
729
  return ret
719
730
 
@@ -982,10 +993,8 @@ class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
982
993
  """
983
994
  msgs = copy.deepcopy(messages)
984
995
 
985
- # Set only vision flag; skip cache_enabled and force_string_serializer
996
+ # Determine vision based on model detection
986
997
  vision_active = self.vision_is_active()
987
- for m in msgs:
988
- m.vision_enabled = vision_active
989
998
 
990
999
  # Assign system instructions as a string, collect input items
991
1000
  instructions: str | None = None
@@ -36,9 +36,11 @@ def select_responses_options(
36
36
  else:
37
37
  out.setdefault("store", False)
38
38
 
39
- # Include encrypted reasoning if stateless
39
+ # Include encrypted reasoning only when the user enables it on the LLM,
40
+ # and only for stateless calls (store=False). Respect user choice.
40
41
  include_list = list(include) if include is not None else []
41
- if not out.get("store", False):
42
+
43
+ if not out.get("store", False) and llm.enable_encrypted_reasoning:
42
44
  if "reasoning.encrypted_content" not in include_list:
43
45
  include_list.append("reasoning.encrypted_content")
44
46
  if include_list:
@@ -16,7 +16,7 @@ from openhands.sdk.logger import get_logger
16
16
  logger = get_logger(__name__)
17
17
 
18
18
  # Helpful alias for listener signature: (attempt_number, max_retries) -> None
19
- RetryListener = Callable[[int, int], None]
19
+ RetryListener = Callable[[int, int, BaseException | None], None]
20
20
 
21
21
 
22
22
  class RetryMixin:
@@ -41,7 +41,12 @@ class RetryMixin:
41
41
  self.log_retry_attempt(retry_state)
42
42
 
43
43
  if retry_listener is not None:
44
- retry_listener(retry_state.attempt_number, num_retries)
44
+ exc = (
45
+ retry_state.outcome.exception()
46
+ if retry_state.outcome is not None
47
+ else None
48
+ )
49
+ retry_listener(retry_state.attempt_number, num_retries, exc)
45
50
 
46
51
  # If there is no outcome or no exception, nothing to tweak.
47
52
  if retry_state.outcome is None:
@@ -120,7 +120,7 @@ class Telemetry(BaseModel):
120
120
 
121
121
  return self.metrics.deep_copy()
122
122
 
123
- def on_error(self, _err: Exception) -> None:
123
+ def on_error(self, _err: BaseException) -> None:
124
124
  # Stub for error tracking / counters
125
125
  return
126
126
 
@@ -1,7 +1,14 @@
1
1
  VERIFIED_OPENAI_MODELS = [
2
+ "gpt-5.1-codex-max",
3
+ "gpt-5.1-codex",
4
+ "gpt-5.1-codex-mini",
2
5
  "gpt-5-codex",
3
6
  "gpt-5-2025-08-07",
4
7
  "gpt-5-mini-2025-08-07",
8
+ "gpt-5.1",
9
+ "gpt-5.1-codex",
10
+ "gpt-5.1-codex-max",
11
+ "gpt-5.1-codex-mini",
5
12
  "o4-mini",
6
13
  "gpt-4o",
7
14
  "gpt-4o-mini",
@@ -33,20 +40,21 @@ VERIFIED_MISTRAL_MODELS = [
33
40
  "devstral-small-2505",
34
41
  "devstral-small-2507",
35
42
  "devstral-medium-2507",
43
+ "devstral-2512",
44
+ "devstral-medium-2512",
36
45
  ]
37
46
 
38
47
  VERIFIED_OPENHANDS_MODELS = [
48
+ "claude-opus-4-5-20251101",
39
49
  "claude-sonnet-4-5-20250929",
50
+ "gpt-5.1-codex-max",
51
+ "gpt-5.1-codex",
52
+ "gpt-5.1",
40
53
  "gemini-3-pro-preview",
41
- "gpt-5-2025-08-07",
42
- "gpt-5-codex",
43
- "claude-haiku-4-5-20251001",
44
- "claude-opus-4-5-20251101",
54
+ "deekseek-chat",
45
55
  "kimi-k2-thinking",
46
- "gpt-5-mini-2025-08-07",
47
- "claude-opus-4-1-20250805",
48
- "devstral-small-2507",
49
- "devstral-medium-2507",
56
+ "devstral-medium-2512",
57
+ "devstral-2512",
50
58
  ]
51
59
 
52
60
 
@@ -13,7 +13,7 @@ from openhands.sdk.tool import (
13
13
  Observation,
14
14
  )
15
15
  from openhands.sdk.tool.schema import Action
16
- from openhands.sdk.utils.visualize import display_dict
16
+ from openhands.sdk.utils.visualize import display_json
17
17
 
18
18
 
19
19
  logger = get_logger(__name__)
@@ -97,7 +97,7 @@ class MCPToolObservation(Observation):
97
97
  # try to see if block.text is a JSON
98
98
  try:
99
99
  parsed = json.loads(block.text)
100
- text.append(display_dict(parsed))
100
+ text.append(display_json(parsed))
101
101
  continue
102
102
  except (json.JSONDecodeError, TypeError):
103
103
  text.append(block.text + "\n")
@@ -0,0 +1,19 @@
1
+ """Secret management module for handling sensitive data.
2
+
3
+ This module provides classes and types for managing secrets in OpenHands.
4
+ """
5
+
6
+ from openhands.sdk.secret.secrets import (
7
+ LookupSecret,
8
+ SecretSource,
9
+ SecretValue,
10
+ StaticSecret,
11
+ )
12
+
13
+
14
+ __all__ = [
15
+ "SecretSource",
16
+ "StaticSecret",
17
+ "LookupSecret",
18
+ "SecretValue",
19
+ ]
@@ -1,3 +1,5 @@
1
+ """Secret sources and types for handling sensitive data."""
2
+
1
3
  from abc import ABC, abstractmethod
2
4
 
3
5
  import httpx
@@ -84,3 +86,7 @@ def _is_secret_header(key: str):
84
86
  if secret in key:
85
87
  return True
86
88
  return False
89
+
90
+
91
+ # Type alias for secret values - can be a plain string or a SecretSource
92
+ SecretValue = str | SecretSource
@@ -29,7 +29,7 @@ def _save_full_content(content: str, save_dir: str, tool_prefix: str) -> str | N
29
29
  """Save full content to the specified directory and return the file path."""
30
30
 
31
31
  save_dir_path = Path(save_dir)
32
- save_dir_path.mkdir(exist_ok=True)
32
+ save_dir_path.mkdir(parents=True, exist_ok=True)
33
33
 
34
34
  # Generate hash-based filename for deduplication
35
35
  content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()[:8]
@@ -2,22 +2,57 @@ from rich.text import Text
2
2
 
3
3
 
4
4
  def display_dict(d) -> Text:
5
- """Create a Rich Text representation of a dictionary."""
5
+ """Create a Rich Text representation of a dictionary.
6
+
7
+ This function is deprecated. Use display_json instead.
8
+ """
9
+ return display_json(d)
10
+
11
+
12
+ def display_json(data) -> Text:
13
+ """Create a Rich Text representation of JSON data.
14
+
15
+ Handles dictionaries, lists, strings, numbers, booleans, and None values.
16
+ """
6
17
  content = Text()
7
- for field_name, field_value in d.items():
8
- if field_value is None:
9
- continue # skip None fields
10
- content.append(f"\n {field_name}: ", style="bold")
11
- if isinstance(field_value, str):
12
- # Handle multiline strings with proper indentation
13
- if "\n" in field_value:
14
- content.append("\n")
15
- for line in field_value.split("\n"):
16
- content.append(f" {line}\n")
18
+
19
+ if isinstance(data, dict):
20
+ for field_name, field_value in data.items():
21
+ if field_value is None:
22
+ continue # skip None fields
23
+ content.append(f"\n {field_name}: ", style="bold")
24
+ if isinstance(field_value, str):
25
+ # Handle multiline strings with proper indentation
26
+ if "\n" in field_value:
27
+ content.append("\n")
28
+ for line in field_value.split("\n"):
29
+ content.append(f" {line}\n")
30
+ else:
31
+ content.append(f'"{field_value}"')
32
+ elif isinstance(field_value, (list, dict)):
33
+ content.append(str(field_value))
34
+ else:
35
+ content.append(str(field_value))
36
+ elif isinstance(data, list):
37
+ content.append(f"[List with {len(data)} items]\n")
38
+ for i, item in enumerate(data):
39
+ content.append(f" [{i}]: ", style="bold")
40
+ if isinstance(item, str):
41
+ content.append(f'"{item}"\n')
17
42
  else:
18
- content.append(f'"{field_value}"')
19
- elif isinstance(field_value, (list, dict)):
20
- content.append(str(field_value))
43
+ content.append(f"{item}\n")
44
+ elif isinstance(data, str):
45
+ # Handle multiline strings with proper indentation
46
+ if "\n" in data:
47
+ content.append("String:\n")
48
+ for line in data.split("\n"):
49
+ content.append(f" {line}\n")
21
50
  else:
22
- content.append(str(field_value))
51
+ content.append(f'"{data}"')
52
+ elif data is None:
53
+ content.append("null")
54
+ else:
55
+ # Handle numbers, booleans, and other JSON primitives
56
+ content.append(str(data))
57
+
23
58
  return content
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.4.1
3
+ Version: 1.5.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
@@ -1,20 +1,20 @@
1
1
  openhands/sdk/__init__.py,sha256=SsB5acHhWvF6e3FlbR72PzZHH9ByJiXc7vnwxBPqmhw,2374
2
2
  openhands/sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  openhands/sdk/agent/__init__.py,sha256=yOn1ZCgTTq2VJlTzKDSzmWVPli1siBzqV89vlEHCwOg,137
4
- openhands/sdk/agent/agent.py,sha256=y8jfExyzgP898_Vr_Fz75Ovg2XTnkNQTgekdOi2S58I,19516
5
- openhands/sdk/agent/base.py,sha256=HmZ_wNXdbsStqxjgkylGFm9B0pkp-0MCjmFTyh_XenM,15780
4
+ openhands/sdk/agent/agent.py,sha256=OCR7JJytiiTHLuFwU3XjMtJx11Y2Io-DamdbMa2mf60,23760
5
+ openhands/sdk/agent/base.py,sha256=39AkDhyUClE6DOSWkmYdouZoHQL7_njVKvx0OQTlHZ8,16131
6
6
  openhands/sdk/agent/utils.py,sha256=alYsAQ611XQ_94ogJacYD22BLbgSJjzd3Xex_b9KuC4,7418
7
7
  openhands/sdk/agent/prompts/in_context_learning_example.j2,sha256=MGB0dPUlh6pwLoR_dBK-M3e5dtETX6C6WNjPcPixZmU,5512
8
8
  openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2,sha256=k3Zwnd7Iq7kL4lo307RDuu1mxWXn6pSLsEdvKEXN3BU,164
9
9
  openhands/sdk/agent/prompts/security_policy.j2,sha256=K56d2aaZ88DI-y2DsMSDaiZRTTnkkzuBLjbzXfKHGA8,993
10
10
  openhands/sdk/agent/prompts/security_risk_assessment.j2,sha256=7o1tk6MIQpVD7sAES-sBhw4ckYLGQydzYnjjNitP5iY,1196
11
- openhands/sdk/agent/prompts/system_prompt.j2,sha256=Cdi9eDy5el1mp5IXlJqr3aqsgRYt3p6yC0Wgw1E8NFQ,7979
11
+ openhands/sdk/agent/prompts/system_prompt.j2,sha256=-emgmh86xJCU8W5Vbsp1zRORWaT0EGegKL13WVYJyBM,8136
12
12
  openhands/sdk/agent/prompts/system_prompt_interactive.j2,sha256=AW3rGuqu82BqbS1XMXVO4Fp-Apa8DPYZV3_nQYkzVtM,1388
13
13
  openhands/sdk/agent/prompts/system_prompt_long_horizon.j2,sha256=_oOHRIer_FSuRrBOSOPpe5Ueo9KgSTba5SPoHHpghCI,2995
14
14
  openhands/sdk/agent/prompts/system_prompt_planning.j2,sha256=wh01KX7yzeUSn5PtG3Ij0keRM6vmQuZZM6I0Fod4DK4,2934
15
15
  openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2,sha256=Yq9H7hHen2-tNsfBq9RlAWpyWsVRpjHhzmziZv8JHs8,5005
16
16
  openhands/sdk/context/__init__.py,sha256=dsOiCbO-eizN7HiGn_ZSSgH-EE841ygwalMzWz9GbVY,557
17
- openhands/sdk/context/agent_context.py,sha256=lEJySpZz2aq9RonsCwsf2eF4bivrCikOqpmnogsiNhk,8370
17
+ openhands/sdk/context/agent_context.py,sha256=CFJRTk0QHcHLB5nL9pKaXiKpVU2UwFNZdYf9p8TArs4,9318
18
18
  openhands/sdk/context/view.py,sha256=j8R4r1PSKFUJR_G4qDyI8ToqyHbWtU0YRKDBm8LJm4I,9260
19
19
  openhands/sdk/context/condenser/__init__.py,sha256=o3O5wEOUak7mzJ3EzY4s6pjUwA_Q5EzLvgNSFM46d50,490
20
20
  openhands/sdk/context/condenser/base.py,sha256=fp1WxZGslE1luwmzFmablZ9Q3h_F7ErBJfCAh6HKpRI,3659
@@ -26,15 +26,15 @@ openhands/sdk/context/prompts/__init__.py,sha256=wC1Qiak4RHY3YS0TuZRmhFGAJ7Vc6CL
26
26
  openhands/sdk/context/prompts/prompt.py,sha256=a2eCR4QSYzgdDxcGkGVktKerAOk-SCb8odTABQHilJE,2684
27
27
  openhands/sdk/context/prompts/templates/ask_agent_template.j2,sha256=VRKWdF2VTJ_Tyway_Wexp8_KlNgAkME94eZelbbsEZI,212
28
28
  openhands/sdk/context/prompts/templates/skill_knowledge_info.j2,sha256=3yNxEEbScAU1w60ix7Ph91wr6p8mjwT-3EGkkZkWT9Q,261
29
- openhands/sdk/context/prompts/templates/system_message_suffix.j2,sha256=JTNvdtH_qbFBTzZVV8NzHtn48BJXNY2dLFgMWj_iwM8,401
29
+ openhands/sdk/context/prompts/templates/system_message_suffix.j2,sha256=YZYYX6XgclDa6GPmJAUjN2UxKlmqCp1gaL61IQzMtDY,2100
30
30
  openhands/sdk/context/skills/__init__.py,sha256=5YmyufIWoLvoUYtcZE_xI4sOtcCs70-CHrblnzavWWY,593
31
31
  openhands/sdk/context/skills/exceptions.py,sha256=tVBSbXTXG32nb1TebVAuzbNNHvn8GScpSM1bHCnQzvY,305
32
32
  openhands/sdk/context/skills/skill.py,sha256=iJpJWiXLHhxd7tmdPo1HcIDqa4emAYkQHgN7KvkQcl0,20407
33
33
  openhands/sdk/context/skills/trigger.py,sha256=ZGaDmMpJghnAEuTTYX6UepsA5nX1CSz83zK1Ox46vMk,756
34
34
  openhands/sdk/context/skills/types.py,sha256=qdakWgfs_88I_cnmVhO5bl3C3s11GvCydq7Q-V53kCI,1662
35
35
  openhands/sdk/conversation/__init__.py,sha256=1-xh49S2KJhtAjkHDaDHU28UWhfQLl3CeFlo179gr00,1402
36
- openhands/sdk/conversation/base.py,sha256=3qUc4_PjrT1IztBX7EJKuypvRVWRIbMmIQ3Fc2lvspI,8308
37
- openhands/sdk/conversation/conversation.py,sha256=GsPK9MWTAWG47J6jQ1vcSD9ER31wbFkRF9zdFmCqz9I,5275
36
+ openhands/sdk/conversation/base.py,sha256=sVpfqtyTVi7_d8rzI_gemZGHusVrNy9S_yNJmTGMEq8,8964
37
+ openhands/sdk/conversation/conversation.py,sha256=bmgjOmPnznJFgEyOguzqUwwLamEEtmPf_YNSLnsUmWM,5253
38
38
  openhands/sdk/conversation/conversation_stats.py,sha256=ZlQ99kgG5YVCrZ4rqJlq63JaiInxX8jqv-q5lS7RN68,3038
39
39
  openhands/sdk/conversation/event_store.py,sha256=he-bwP823s5zAIdua_0ZgkkHQCJoAqbtV2SN0hibX30,5207
40
40
  openhands/sdk/conversation/events_list_base.py,sha256=n_YvgbhBPOPDbw4Kp68J0EKFM39vg95ng09GMfTz29s,505
@@ -42,16 +42,15 @@ openhands/sdk/conversation/exceptions.py,sha256=C3pN3MJJIYdhcMHMqtOmVkR1BhVe3pfx
42
42
  openhands/sdk/conversation/fifo_lock.py,sha256=nY5RsobNvVXBbAzwjqIxyQwPUh0AzffbTZw4PewhTTI,4240
43
43
  openhands/sdk/conversation/persistence_const.py,sha256=om3pOQa5sGK8t_NUYb3Tz-7sKeu531gaS1e7iCaqWmo,218
44
44
  openhands/sdk/conversation/response_utils.py,sha256=rPlC3cDSmoQte6NZ0kK6h6-9ho5cbF8jEw-DiyEhgIM,1548
45
- openhands/sdk/conversation/secret_registry.py,sha256=37kk-aaeQ_XKZDXHpNJ2oTOWsElL4Q9jI_6qd3A774I,4500
46
- openhands/sdk/conversation/secret_source.py,sha256=1itHqTgwu1yZKGZ46HR0X08MLU2Y_rWa2ahzbqxH4k0,2568
45
+ openhands/sdk/conversation/secret_registry.py,sha256=6fY1zRxb55rC4uIMFcR0lDssIyyjaPh9pCWGqDikrek,4446
47
46
  openhands/sdk/conversation/serialization_diff.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
47
  openhands/sdk/conversation/state.py,sha256=EpP_0MAWv14gEIBvIlpdIxtxLCzN1n2mgzSzQNRpCY8,13046
49
48
  openhands/sdk/conversation/stuck_detector.py,sha256=PZF0HWC6G0SUud_U3hiv5r4AqfJJMw5-ookEWVOY5sY,10866
50
49
  openhands/sdk/conversation/title_utils.py,sha256=j40-dP-Oes-mhU2xUC7fCC8cB0wkMdbbDJU7WLHiVIo,7063
51
50
  openhands/sdk/conversation/types.py,sha256=CMCCJz6fSfWgaAgWXeorEDC8VSXyyplyiMlpcueszT8,423
52
51
  openhands/sdk/conversation/impl/__init__.py,sha256=DmDFyNR4RU8eiMocKf2j9eBQomipP-rrJgU1LoVWTDA,220
53
- openhands/sdk/conversation/impl/local_conversation.py,sha256=R0iTc1oCaOBiBUH3Ei7RNO1JMS_7nEuBlCr3ILUosU0,22594
54
- openhands/sdk/conversation/impl/remote_conversation.py,sha256=9_yCDHO5KKODYHVzUt0mJnsSVcDE6Tv3TJvGULilcyE,28536
52
+ openhands/sdk/conversation/impl/local_conversation.py,sha256=cqA_uek_BdY9xkAnGcqFG-GtU77-SNx7DlhYY7bELGQ,24592
53
+ openhands/sdk/conversation/impl/remote_conversation.py,sha256=r7kj82Ng0g7ov8YFVmzFw-lhMduzXEOSgZPZTeTYHUg,29230
55
54
  openhands/sdk/conversation/visualizer/__init__.py,sha256=0LXpKlt2eJcrqP1z6jQP_nLx23V8ErnQkKYSxvUp0_A,275
56
55
  openhands/sdk/conversation/visualizer/base.py,sha256=77DdRdHAPSESxRCYyRRSOK7ROBlljscxogkFFr4YgM0,2323
57
56
  openhands/sdk/conversation/visualizer/default.py,sha256=k-3-l1j8H_EOEn_pfzsUczcc4JDhQni7AUQZgZ2TpB4,11885
@@ -85,7 +84,7 @@ openhands/sdk/io/base.py,sha256=kAcX0chfCswakiieJlKiHWoJgL3zOtaQauRqMPNYfW8,1355
85
84
  openhands/sdk/io/local.py,sha256=H1wjnBS0EkBJxEatWqNpteL1bPBHoetcnhrIeou4uEY,2991
86
85
  openhands/sdk/io/memory.py,sha256=XIsdXsSyF-PzoYVmvJuO7Vtz-k3D5jMOFoZ5gHw8tbA,1712
87
86
  openhands/sdk/llm/__init__.py,sha256=k8UneyfoDUMe0lSP4GSlYzrL2Fe3MkDUKpSg2OIDi_I,1206
88
- openhands/sdk/llm/llm.py,sha256=7cpdaXvsgafim2ZXI5umjN3P5ND9xk1GjktKRbrWGqo,44308
87
+ openhands/sdk/llm/llm.py,sha256=Xd0NsCjn59bYVdgUih31JyBiEebKOJmPYMhYWdYLve0,44677
89
88
  openhands/sdk/llm/llm_registry.py,sha256=DL9yqSbAM7OBkzdIChLuxG2qk_oElW2tC2xem6mq0F8,3530
90
89
  openhands/sdk/llm/llm_response.py,sha256=DaBVBkij4Sz-RsYhRb3UUcvJCTzCBcOYQ9IhFwN4ukI,1988
91
90
  openhands/sdk/llm/message.py,sha256=zVcmHL4z99Bm0_qHz0hhtDh8a9r8Y1GtcfCgL5yBsME,25429
@@ -99,7 +98,7 @@ openhands/sdk/llm/mixins/non_native_fc.py,sha256=ymPELReJtFsBJCweEpfx2njyd6NCG9m
99
98
  openhands/sdk/llm/options/__init__.py,sha256=EntvOWC5kwDoTMXXMkYuoWMQ13hD8YtC9CEMCtnKj7o,54
100
99
  openhands/sdk/llm/options/chat_options.py,sha256=YrQrMKUj9Sb0ZdzJMoB11PwD7hzdLBDGrOMWfgZQ10s,3452
101
100
  openhands/sdk/llm/options/common.py,sha256=qFcPuZF_c4rmH1bgGG8Qp6TJ4YWpv9IFzfLZRRiik9M,580
102
- openhands/sdk/llm/options/responses_options.py,sha256=xrBEc62PFjLeM9bGyXtXrQ6GgYDzhJfGha8Nhi5Oci8,2166
101
+ openhands/sdk/llm/options/responses_options.py,sha256=1ff_06XcORneDQUjwrEM6tel9HLI9_veMcoKHYAKLG8,2302
103
102
  openhands/sdk/llm/router/__init__.py,sha256=N8qldpGdLLCWZzn5Rz2y8AheSoCTQLGkLOBDCFNMJRA,261
104
103
  openhands/sdk/llm/router/base.py,sha256=9mUUpv-zgT6HVKmJ87Q5e5VncoKi8hHd9aP8j9cJI_g,4139
105
104
  openhands/sdk/llm/router/impl/multimodal.py,sha256=uKFVm7b3Jr0xCC1lvP-cVn-fIkTv24-pK3xKlOJahz4,3034
@@ -107,22 +106,24 @@ openhands/sdk/llm/router/impl/random.py,sha256=oBHoFTBMa9OeDyg-rV4siLCkN6rKYL0uD
107
106
  openhands/sdk/llm/utils/metrics.py,sha256=4zD0Hkc9Oc4qcDcVZUX13RyggyObshUbz4Ik9W1uIw4,11592
108
107
  openhands/sdk/llm/utils/model_features.py,sha256=JMGShnYBplGL6O6f19YC2EqI3dIviNN_58TFbflBnlA,4607
109
108
  openhands/sdk/llm/utils/model_info.py,sha256=1mFYA7OcEyUB6k1doao8_w1XT7UMM_DAm57HcTpKkLw,2628
110
- openhands/sdk/llm/utils/retry_mixin.py,sha256=yIkUxgtym4ouOci0o1u1BJnThUt05njblhC0b2Tvmb8,4559
111
- openhands/sdk/llm/utils/telemetry.py,sha256=BGzikbw1DAj96Bo60D7XIGh034zKIbO1zRx597_3TR4,13966
109
+ openhands/sdk/llm/utils/retry_mixin.py,sha256=M-hXp8EwP1FjNN6tgHiv133BtUQgRr9Kz_ZWxeAJLGA,4765
110
+ openhands/sdk/llm/utils/telemetry.py,sha256=_csSRWg80djE27QN9Mg3palVTP-LJ7BbttOaHQYykZU,13970
112
111
  openhands/sdk/llm/utils/unverified_models.py,sha256=SmYrX_WxXOJBanTviztqy1xPjOcLY4i3qvwNBEga_Dk,4797
113
- openhands/sdk/llm/utils/verified_models.py,sha256=add09fF6MHOCBS4Wp2BxLYYfijMtzkq1IFc3GNZgMWU,1357
112
+ openhands/sdk/llm/utils/verified_models.py,sha256=567qe5E3Y9Or-317fDULpTadTobcHOon3QCyBDh3MxE,1504
114
113
  openhands/sdk/logger/__init__.py,sha256=vZvFDYfW01Y8Act3tveMs3XxTysJlt4HeT-n6X_ujYk,330
115
114
  openhands/sdk/logger/logger.py,sha256=kSpeol92dKesm24ZxVyYbtAxeYccmuFZJw3qNO9X-t4,6513
116
115
  openhands/sdk/logger/rolling.py,sha256=E6oy0asgmOhZHoWlSCw0QK1PKnS6kvtxjoWLAsqlGvs,3440
117
116
  openhands/sdk/mcp/__init__.py,sha256=-wQbZ405PjVRCBtSfirp4jsiRohd7IJAyAdicZ-M8Ok,588
118
117
  openhands/sdk/mcp/client.py,sha256=CLkFImydorlT_RBTE5UxvRNGMlc-uO3wvupoDzB5E20,2420
119
- openhands/sdk/mcp/definition.py,sha256=PspdEd-0vX1onjk2ha9cfQTU072MyVK0oG86zlviPpU,3424
118
+ openhands/sdk/mcp/definition.py,sha256=vFLQeLW99fBzPGR7X7_1GzTmIHlSVAbmsg3elhhkp5U,3424
120
119
  openhands/sdk/mcp/exceptions.py,sha256=N4g7Wju420TQJ7hmck1e5UblWbC_7Torb-UTFj1GN70,448
121
120
  openhands/sdk/mcp/tool.py,sha256=GJElHJ7gVLSfAacmaHm2PHHiV9Gb1_siu1PQrDJo7_U,10187
122
121
  openhands/sdk/mcp/utils.py,sha256=AazuWwhEwHBs1JKXfNVJDArkgQfmuj4MrPQwxDo52ds,2919
123
122
  openhands/sdk/observability/__init__.py,sha256=JKHDWoj01igaCUChdXgKmstESiMmbAK9CN5XzT2H7fo,122
124
123
  openhands/sdk/observability/laminar.py,sha256=U2AbSWpPcUYdrzx__-BEg4LPW13f1l-Hk-V4qeBmE3k,5053
125
124
  openhands/sdk/observability/utils.py,sha256=N0p8ACs2WKS1PyFPjiU-PPng6WsAe15ZpFViFAw1gO0,576
125
+ openhands/sdk/secret/__init__.py,sha256=Y-M2WPfYLfVYZSFZGTf6MDOnjsL5SayETtA4t9X0gjw,348
126
+ openhands/sdk/secret/secrets.py,sha256=1PkD9oXJJlPgsN4D3brv3_HjIc2IQGy5BbcqZKJ_8Ew,2737
126
127
  openhands/sdk/security/__init__.py,sha256=x2-a0fsImxSM2msHl7gV0fENWyZbpY7-HW_CBwNrPZY,89
127
128
  openhands/sdk/security/analyzer.py,sha256=V6Fowh83sfEr1SdwK3UW1PNQHahHxwHfla0KndTombk,3994
128
129
  openhands/sdk/security/confirmation_policy.py,sha256=bsrIOfo3QOXNyKrmUVzQz070F3xbJ7uZp9WTr4RMdl8,2145
@@ -147,8 +148,8 @@ openhands/sdk/utils/json.py,sha256=hHAA7i7NCJrQhb5WWSsoT0nvmJUi0koyBdbviFrfdcM,1
147
148
  openhands/sdk/utils/models.py,sha256=tOTGa4O1vHZgHvOp2mNv5V4hM3fBSM6Sswv-0oYVyJQ,20676
148
149
  openhands/sdk/utils/pydantic_diff.py,sha256=vOy4M1XKKDkCzp7RXBvnibvPYWL8fNEDtWNUn_-N1yg,2732
149
150
  openhands/sdk/utils/pydantic_secrets.py,sha256=B9njRdijnqO4g-GDCWRsWd-TZc5GTjMncDEPMFP_aUE,2186
150
- openhands/sdk/utils/truncate.py,sha256=wrPdD1557teSR0A66z9roYAINp_cWYYwS6Ku29Z7b8M,4376
151
- openhands/sdk/utils/visualize.py,sha256=ZRZTA0epdiOA7vdk-EdGV6Yb5t8rz6ZzrargH7MUTEQ,845
151
+ openhands/sdk/utils/truncate.py,sha256=LYRA45CBR7daSC03gs80nu7PYdROh943_niOpaoquKU,4390
152
+ openhands/sdk/utils/visualize.py,sha256=4x32eU-OiSZTKpXWIche7QppMS1vxcZjP-kASqE99X4,2004
152
153
  openhands/sdk/workspace/__init__.py,sha256=09huA83IZJZU8XZSVzSgZwfUv-slzYwofnUjLgqt7B8,401
153
154
  openhands/sdk/workspace/base.py,sha256=4_oBXbjspa2Oh2wpgbcQMyrshi7prhmOL1LY97NowgQ,4590
154
155
  openhands/sdk/workspace/local.py,sha256=N_cW_E5BfctIpgwU-OcRQIqKLn4ibxNwjL4c8iYjU6E,6253
@@ -158,7 +159,7 @@ openhands/sdk/workspace/remote/__init__.py,sha256=eKkj6NOESMUBGDVC6_L2Wfuc4K6G-m
158
159
  openhands/sdk/workspace/remote/async_remote_workspace.py,sha256=ftv1Vdx4mmM3AjygJpemMJGvhaQel7ORxdQVk12z4ZE,5061
159
160
  openhands/sdk/workspace/remote/base.py,sha256=72C9MZV7ch5n6oHNvFMo6irW7b6Le8n4gk3yFuc0798,5605
160
161
  openhands/sdk/workspace/remote/remote_workspace_mixin.py,sha256=CzHfnLUIra5sgPkP9kcggb1vHGOPpYQzLsHvGO2rRt0,10963
161
- openhands_sdk-1.4.1.dist-info/METADATA,sha256=9K8ieOX7Xf0snpoeuCY9KmXkd5GTHkg_3EpgTT6vGLo,545
162
- openhands_sdk-1.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
163
- openhands_sdk-1.4.1.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
164
- openhands_sdk-1.4.1.dist-info/RECORD,,
162
+ openhands_sdk-1.5.1.dist-info/METADATA,sha256=NkfbFaqX0zqylPJBCkNeSAdDBl5F4LzGU_x0HB5Vvvk,545
163
+ openhands_sdk-1.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
164
+ openhands_sdk-1.5.1.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
165
+ openhands_sdk-1.5.1.dist-info/RECORD,,