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