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.
- openhands/sdk/agent/agent.py +99 -3
- openhands/sdk/agent/base.py +6 -0
- openhands/sdk/agent/prompts/system_prompt.j2 +1 -0
- openhands/sdk/context/agent_context.py +26 -1
- openhands/sdk/context/prompts/templates/system_message_suffix.j2 +16 -0
- openhands/sdk/conversation/base.py +17 -0
- openhands/sdk/conversation/conversation.py +1 -1
- openhands/sdk/conversation/impl/local_conversation.py +50 -0
- openhands/sdk/conversation/impl/remote_conversation.py +15 -0
- openhands/sdk/conversation/secret_registry.py +1 -6
- openhands/sdk/llm/llm.py +20 -11
- openhands/sdk/llm/options/responses_options.py +4 -2
- openhands/sdk/llm/utils/retry_mixin.py +7 -2
- openhands/sdk/llm/utils/telemetry.py +1 -1
- openhands/sdk/llm/utils/verified_models.py +16 -8
- openhands/sdk/mcp/definition.py +2 -2
- openhands/sdk/secret/__init__.py +19 -0
- openhands/sdk/{conversation/secret_source.py → secret/secrets.py} +6 -0
- openhands/sdk/utils/truncate.py +1 -1
- openhands/sdk/utils/visualize.py +50 -15
- {openhands_sdk-1.4.1.dist-info → openhands_sdk-1.5.1.dist-info}/METADATA +1 -1
- {openhands_sdk-1.4.1.dist-info → openhands_sdk-1.5.1.dist-info}/RECORD +24 -23
- {openhands_sdk-1.4.1.dist-info → openhands_sdk-1.5.1.dist-info}/WHEEL +0 -0
- {openhands_sdk-1.4.1.dist-info → openhands_sdk-1.5.1.dist-info}/top_level.txt +0 -0
openhands/sdk/agent/agent.py
CHANGED
|
@@ -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;
|
|
201
|
-
|
|
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
|
+
)
|
openhands/sdk/agent/base.py
CHANGED
|
@@ -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
|
-
|
|
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=
|
|
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[
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"devstral-small-2507",
|
|
49
|
-
"devstral-medium-2507",
|
|
56
|
+
"devstral-medium-2512",
|
|
57
|
+
"devstral-2512",
|
|
50
58
|
]
|
|
51
59
|
|
|
52
60
|
|
openhands/sdk/mcp/definition.py
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
openhands/sdk/utils/truncate.py
CHANGED
|
@@ -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]
|
openhands/sdk/utils/visualize.py
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
content.append(
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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(
|
|
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,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=
|
|
5
|
-
openhands/sdk/agent/base.py,sha256=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
37
|
-
openhands/sdk/conversation/conversation.py,sha256=
|
|
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=
|
|
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=
|
|
54
|
-
openhands/sdk/conversation/impl/remote_conversation.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
111
|
-
openhands/sdk/llm/utils/telemetry.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
151
|
-
openhands/sdk/utils/visualize.py,sha256=
|
|
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.
|
|
162
|
-
openhands_sdk-1.
|
|
163
|
-
openhands_sdk-1.
|
|
164
|
-
openhands_sdk-1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|