openhands-sdk 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openhands/sdk/__init__.py +111 -0
- openhands/sdk/agent/__init__.py +8 -0
- openhands/sdk/agent/agent.py +607 -0
- openhands/sdk/agent/base.py +454 -0
- openhands/sdk/agent/prompts/in_context_learning_example.j2 +169 -0
- openhands/sdk/agent/prompts/in_context_learning_example_suffix.j2 +3 -0
- openhands/sdk/agent/prompts/model_specific/anthropic_claude.j2 +3 -0
- openhands/sdk/agent/prompts/model_specific/google_gemini.j2 +1 -0
- openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5-codex.j2 +3 -0
- openhands/sdk/agent/prompts/model_specific/openai_gpt/gpt-5.j2 +3 -0
- openhands/sdk/agent/prompts/security_policy.j2 +22 -0
- openhands/sdk/agent/prompts/security_risk_assessment.j2 +21 -0
- openhands/sdk/agent/prompts/self_documentation.j2 +15 -0
- openhands/sdk/agent/prompts/system_prompt.j2 +132 -0
- openhands/sdk/agent/prompts/system_prompt_interactive.j2 +14 -0
- openhands/sdk/agent/prompts/system_prompt_long_horizon.j2 +40 -0
- openhands/sdk/agent/prompts/system_prompt_planning.j2 +40 -0
- openhands/sdk/agent/prompts/system_prompt_tech_philosophy.j2 +122 -0
- openhands/sdk/agent/utils.py +223 -0
- openhands/sdk/context/__init__.py +28 -0
- openhands/sdk/context/agent_context.py +240 -0
- openhands/sdk/context/condenser/__init__.py +18 -0
- openhands/sdk/context/condenser/base.py +95 -0
- openhands/sdk/context/condenser/llm_summarizing_condenser.py +89 -0
- openhands/sdk/context/condenser/no_op_condenser.py +13 -0
- openhands/sdk/context/condenser/pipeline_condenser.py +55 -0
- openhands/sdk/context/condenser/prompts/summarizing_prompt.j2 +59 -0
- openhands/sdk/context/prompts/__init__.py +6 -0
- openhands/sdk/context/prompts/prompt.py +114 -0
- openhands/sdk/context/prompts/templates/ask_agent_template.j2 +11 -0
- openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +8 -0
- openhands/sdk/context/prompts/templates/system_message_suffix.j2 +32 -0
- openhands/sdk/context/skills/__init__.py +28 -0
- openhands/sdk/context/skills/exceptions.py +11 -0
- openhands/sdk/context/skills/skill.py +630 -0
- openhands/sdk/context/skills/trigger.py +36 -0
- openhands/sdk/context/skills/types.py +48 -0
- openhands/sdk/context/view.py +306 -0
- openhands/sdk/conversation/__init__.py +40 -0
- openhands/sdk/conversation/base.py +281 -0
- openhands/sdk/conversation/conversation.py +146 -0
- openhands/sdk/conversation/conversation_stats.py +85 -0
- openhands/sdk/conversation/event_store.py +157 -0
- openhands/sdk/conversation/events_list_base.py +17 -0
- openhands/sdk/conversation/exceptions.py +50 -0
- openhands/sdk/conversation/fifo_lock.py +133 -0
- openhands/sdk/conversation/impl/__init__.py +5 -0
- openhands/sdk/conversation/impl/local_conversation.py +620 -0
- openhands/sdk/conversation/impl/remote_conversation.py +883 -0
- openhands/sdk/conversation/persistence_const.py +9 -0
- openhands/sdk/conversation/response_utils.py +41 -0
- openhands/sdk/conversation/secret_registry.py +126 -0
- openhands/sdk/conversation/serialization_diff.py +0 -0
- openhands/sdk/conversation/state.py +352 -0
- openhands/sdk/conversation/stuck_detector.py +311 -0
- openhands/sdk/conversation/title_utils.py +191 -0
- openhands/sdk/conversation/types.py +45 -0
- openhands/sdk/conversation/visualizer/__init__.py +12 -0
- openhands/sdk/conversation/visualizer/base.py +67 -0
- openhands/sdk/conversation/visualizer/default.py +373 -0
- openhands/sdk/critic/__init__.py +15 -0
- openhands/sdk/critic/base.py +38 -0
- openhands/sdk/critic/impl/__init__.py +12 -0
- openhands/sdk/critic/impl/agent_finished.py +83 -0
- openhands/sdk/critic/impl/empty_patch.py +49 -0
- openhands/sdk/critic/impl/pass_critic.py +42 -0
- openhands/sdk/event/__init__.py +42 -0
- openhands/sdk/event/base.py +149 -0
- openhands/sdk/event/condenser.py +82 -0
- openhands/sdk/event/conversation_error.py +25 -0
- openhands/sdk/event/conversation_state.py +104 -0
- openhands/sdk/event/llm_completion_log.py +39 -0
- openhands/sdk/event/llm_convertible/__init__.py +20 -0
- openhands/sdk/event/llm_convertible/action.py +139 -0
- openhands/sdk/event/llm_convertible/message.py +142 -0
- openhands/sdk/event/llm_convertible/observation.py +141 -0
- openhands/sdk/event/llm_convertible/system.py +61 -0
- openhands/sdk/event/token.py +16 -0
- openhands/sdk/event/types.py +11 -0
- openhands/sdk/event/user_action.py +21 -0
- openhands/sdk/git/exceptions.py +43 -0
- openhands/sdk/git/git_changes.py +249 -0
- openhands/sdk/git/git_diff.py +129 -0
- openhands/sdk/git/models.py +21 -0
- openhands/sdk/git/utils.py +189 -0
- openhands/sdk/io/__init__.py +6 -0
- openhands/sdk/io/base.py +48 -0
- openhands/sdk/io/local.py +82 -0
- openhands/sdk/io/memory.py +54 -0
- openhands/sdk/llm/__init__.py +45 -0
- openhands/sdk/llm/exceptions/__init__.py +45 -0
- openhands/sdk/llm/exceptions/classifier.py +50 -0
- openhands/sdk/llm/exceptions/mapping.py +54 -0
- openhands/sdk/llm/exceptions/types.py +101 -0
- openhands/sdk/llm/llm.py +1140 -0
- openhands/sdk/llm/llm_registry.py +122 -0
- openhands/sdk/llm/llm_response.py +59 -0
- openhands/sdk/llm/message.py +656 -0
- openhands/sdk/llm/mixins/fn_call_converter.py +1243 -0
- openhands/sdk/llm/mixins/non_native_fc.py +93 -0
- openhands/sdk/llm/options/__init__.py +1 -0
- openhands/sdk/llm/options/chat_options.py +93 -0
- openhands/sdk/llm/options/common.py +19 -0
- openhands/sdk/llm/options/responses_options.py +67 -0
- openhands/sdk/llm/router/__init__.py +10 -0
- openhands/sdk/llm/router/base.py +117 -0
- openhands/sdk/llm/router/impl/multimodal.py +76 -0
- openhands/sdk/llm/router/impl/random.py +22 -0
- openhands/sdk/llm/streaming.py +9 -0
- openhands/sdk/llm/utils/metrics.py +312 -0
- openhands/sdk/llm/utils/model_features.py +191 -0
- openhands/sdk/llm/utils/model_info.py +90 -0
- openhands/sdk/llm/utils/model_prompt_spec.py +98 -0
- openhands/sdk/llm/utils/retry_mixin.py +128 -0
- openhands/sdk/llm/utils/telemetry.py +362 -0
- openhands/sdk/llm/utils/unverified_models.py +156 -0
- openhands/sdk/llm/utils/verified_models.py +66 -0
- openhands/sdk/logger/__init__.py +22 -0
- openhands/sdk/logger/logger.py +195 -0
- openhands/sdk/logger/rolling.py +113 -0
- openhands/sdk/mcp/__init__.py +24 -0
- openhands/sdk/mcp/client.py +76 -0
- openhands/sdk/mcp/definition.py +106 -0
- openhands/sdk/mcp/exceptions.py +19 -0
- openhands/sdk/mcp/tool.py +270 -0
- openhands/sdk/mcp/utils.py +83 -0
- openhands/sdk/observability/__init__.py +4 -0
- openhands/sdk/observability/laminar.py +166 -0
- openhands/sdk/observability/utils.py +20 -0
- openhands/sdk/py.typed +0 -0
- openhands/sdk/secret/__init__.py +19 -0
- openhands/sdk/secret/secrets.py +92 -0
- openhands/sdk/security/__init__.py +6 -0
- openhands/sdk/security/analyzer.py +111 -0
- openhands/sdk/security/confirmation_policy.py +61 -0
- openhands/sdk/security/llm_analyzer.py +29 -0
- openhands/sdk/security/risk.py +100 -0
- openhands/sdk/tool/__init__.py +34 -0
- openhands/sdk/tool/builtins/__init__.py +34 -0
- openhands/sdk/tool/builtins/finish.py +106 -0
- openhands/sdk/tool/builtins/think.py +117 -0
- openhands/sdk/tool/registry.py +161 -0
- openhands/sdk/tool/schema.py +276 -0
- openhands/sdk/tool/spec.py +39 -0
- openhands/sdk/tool/tool.py +481 -0
- openhands/sdk/utils/__init__.py +22 -0
- openhands/sdk/utils/async_executor.py +115 -0
- openhands/sdk/utils/async_utils.py +39 -0
- openhands/sdk/utils/cipher.py +68 -0
- openhands/sdk/utils/command.py +90 -0
- openhands/sdk/utils/deprecation.py +166 -0
- openhands/sdk/utils/github.py +44 -0
- openhands/sdk/utils/json.py +48 -0
- openhands/sdk/utils/models.py +570 -0
- openhands/sdk/utils/paging.py +63 -0
- openhands/sdk/utils/pydantic_diff.py +85 -0
- openhands/sdk/utils/pydantic_secrets.py +64 -0
- openhands/sdk/utils/truncate.py +117 -0
- openhands/sdk/utils/visualize.py +58 -0
- openhands/sdk/workspace/__init__.py +17 -0
- openhands/sdk/workspace/base.py +158 -0
- openhands/sdk/workspace/local.py +189 -0
- openhands/sdk/workspace/models.py +35 -0
- openhands/sdk/workspace/remote/__init__.py +8 -0
- openhands/sdk/workspace/remote/async_remote_workspace.py +149 -0
- openhands/sdk/workspace/remote/base.py +164 -0
- openhands/sdk/workspace/remote/remote_workspace_mixin.py +323 -0
- openhands/sdk/workspace/workspace.py +49 -0
- openhands_sdk-1.7.0.dist-info/METADATA +17 -0
- openhands_sdk-1.7.0.dist-info/RECORD +172 -0
- openhands_sdk-1.7.0.dist-info/WHEEL +5 -0
- openhands_sdk-1.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# prompt_utils.py
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
|
|
7
|
+
from jinja2 import (
|
|
8
|
+
BaseLoader,
|
|
9
|
+
Environment,
|
|
10
|
+
FileSystemBytecodeCache,
|
|
11
|
+
Template,
|
|
12
|
+
TemplateNotFound,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FlexibleFileSystemLoader(BaseLoader):
|
|
17
|
+
"""A Jinja2 loader that supports both relative paths (within a base directory)
|
|
18
|
+
and absolute paths anywhere on the filesystem.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, searchpath: str):
|
|
22
|
+
self.searchpath = os.path.abspath(searchpath)
|
|
23
|
+
|
|
24
|
+
def get_source(self, environment, template): # noqa: ARG002
|
|
25
|
+
# If template is an absolute path, use it directly
|
|
26
|
+
if os.path.isabs(template):
|
|
27
|
+
path = template
|
|
28
|
+
else:
|
|
29
|
+
# Otherwise, look for it in the searchpath
|
|
30
|
+
path = os.path.join(self.searchpath, template)
|
|
31
|
+
|
|
32
|
+
if not os.path.exists(path):
|
|
33
|
+
raise TemplateNotFound(template)
|
|
34
|
+
|
|
35
|
+
mtime = os.path.getmtime(path)
|
|
36
|
+
with open(path, encoding="utf-8") as f:
|
|
37
|
+
source = f.read()
|
|
38
|
+
|
|
39
|
+
def uptodate():
|
|
40
|
+
try:
|
|
41
|
+
return os.path.getmtime(path) == mtime
|
|
42
|
+
except OSError:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
return source, path, uptodate
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def refine(text: str) -> str:
|
|
49
|
+
if sys.platform == "win32":
|
|
50
|
+
text = re.sub(r"\bterminal\b", "execute_powershell", text, flags=re.IGNORECASE)
|
|
51
|
+
text = re.sub(
|
|
52
|
+
r"(?<!execute_)(?<!_)\bbash\b", "powershell", text, flags=re.IGNORECASE
|
|
53
|
+
)
|
|
54
|
+
return text
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@lru_cache(maxsize=64)
|
|
58
|
+
def _get_env(prompt_dir: str) -> Environment:
|
|
59
|
+
if not prompt_dir:
|
|
60
|
+
raise ValueError("prompt_dir is required")
|
|
61
|
+
# BytecodeCache avoids reparsing templates across processes
|
|
62
|
+
# Use user-specific cache directory to avoid permission issues
|
|
63
|
+
# in multi-user environments
|
|
64
|
+
cache_folder = os.path.join(os.path.expanduser("~"), ".openhands", "cache", "jinja")
|
|
65
|
+
os.makedirs(cache_folder, exist_ok=True)
|
|
66
|
+
bcc = FileSystemBytecodeCache(directory=cache_folder)
|
|
67
|
+
env = Environment(
|
|
68
|
+
loader=FlexibleFileSystemLoader(prompt_dir),
|
|
69
|
+
bytecode_cache=bcc,
|
|
70
|
+
autoescape=False,
|
|
71
|
+
)
|
|
72
|
+
# Optional: expose refine as a filter so templates can use {{ text|refine }}
|
|
73
|
+
env.filters["refine"] = refine
|
|
74
|
+
return env
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@lru_cache(maxsize=256)
|
|
78
|
+
def _get_template(prompt_dir: str, template_name: str) -> Template:
|
|
79
|
+
env = _get_env(prompt_dir)
|
|
80
|
+
try:
|
|
81
|
+
return env.get_template(template_name)
|
|
82
|
+
except Exception:
|
|
83
|
+
raise FileNotFoundError(
|
|
84
|
+
f"Prompt file {os.path.join(prompt_dir, template_name)} not found"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def render_template(prompt_dir: str, template_name: str, **ctx) -> str:
|
|
89
|
+
"""Render a Jinja2 template.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
prompt_dir: The base directory for relative template paths.
|
|
93
|
+
template_name: The template filename. Can be either:
|
|
94
|
+
- A relative filename (e.g., "system_prompt.j2") loaded from prompt_dir
|
|
95
|
+
- An absolute path (e.g., "/path/to/custom_prompt.j2")
|
|
96
|
+
**ctx: Template context variables.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Rendered template string.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
FileNotFoundError: If the template file cannot be found.
|
|
103
|
+
"""
|
|
104
|
+
# If template_name is an absolute path, extract directory and filename
|
|
105
|
+
if os.path.isabs(template_name):
|
|
106
|
+
# Check if the file exists before trying to load it
|
|
107
|
+
if not os.path.isfile(template_name):
|
|
108
|
+
raise FileNotFoundError(f"Prompt file {template_name} not found")
|
|
109
|
+
actual_dir = os.path.dirname(template_name)
|
|
110
|
+
actual_filename = os.path.basename(template_name)
|
|
111
|
+
tpl = _get_template(actual_dir, actual_filename)
|
|
112
|
+
else:
|
|
113
|
+
tpl = _get_template(prompt_dir, template_name)
|
|
114
|
+
return refine(tpl.render(**ctx).strip())
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% for agent_info in triggered_agents %}
|
|
2
|
+
<EXTRA_INFO>
|
|
3
|
+
The following information has been included based on a keyword match for "{{ agent_info.trigger }}".
|
|
4
|
+
It may or may not be relevant to the user's request.
|
|
5
|
+
|
|
6
|
+
{{ agent_info.content }}
|
|
7
|
+
</EXTRA_INFO>
|
|
8
|
+
{% endfor %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{% if repo_skills %}
|
|
2
|
+
<REPO_CONTEXT>
|
|
3
|
+
The following information has been included based on several files defined in user's repository.
|
|
4
|
+
Please follow them while working.
|
|
5
|
+
|
|
6
|
+
{% for agent_info in repo_skills %}
|
|
7
|
+
[BEGIN context from [{{ agent_info.name }}]]
|
|
8
|
+
{{ agent_info.content }}
|
|
9
|
+
[END Context]
|
|
10
|
+
{% endfor %}
|
|
11
|
+
</REPO_CONTEXT>
|
|
12
|
+
{% endif %}
|
|
13
|
+
{% if system_message_suffix %}
|
|
14
|
+
|
|
15
|
+
{{ system_message_suffix }}
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if secret_infos %}
|
|
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_info in secret_infos %}
|
|
29
|
+
* **${{ secret_info.name }}**{% if secret_info.description %} - {{ secret_info.description }}{% endif %}
|
|
30
|
+
{% endfor %}
|
|
31
|
+
</CUSTOM_SECRETS>
|
|
32
|
+
{% endif %}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from openhands.sdk.context.skills.exceptions import SkillValidationError
|
|
2
|
+
from openhands.sdk.context.skills.skill import (
|
|
3
|
+
Skill,
|
|
4
|
+
load_project_skills,
|
|
5
|
+
load_public_skills,
|
|
6
|
+
load_skills_from_dir,
|
|
7
|
+
load_user_skills,
|
|
8
|
+
)
|
|
9
|
+
from openhands.sdk.context.skills.trigger import (
|
|
10
|
+
BaseTrigger,
|
|
11
|
+
KeywordTrigger,
|
|
12
|
+
TaskTrigger,
|
|
13
|
+
)
|
|
14
|
+
from openhands.sdk.context.skills.types import SkillKnowledge
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"Skill",
|
|
19
|
+
"BaseTrigger",
|
|
20
|
+
"KeywordTrigger",
|
|
21
|
+
"TaskTrigger",
|
|
22
|
+
"SkillKnowledge",
|
|
23
|
+
"load_skills_from_dir",
|
|
24
|
+
"load_user_skills",
|
|
25
|
+
"load_project_skills",
|
|
26
|
+
"load_public_skills",
|
|
27
|
+
"SkillValidationError",
|
|
28
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class SkillError(Exception):
|
|
2
|
+
"""Base exception for all skill errors."""
|
|
3
|
+
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SkillValidationError(SkillError):
|
|
8
|
+
"""Raised when there's a validation error in skill metadata."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, message: str = "Skill validation failed") -> None:
|
|
11
|
+
super().__init__(message)
|