openhands-agent-server 1.22.0__tar.gz → 1.22.1__tar.gz

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 (59) hide show
  1. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/PKG-INFO +1 -1
  2. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/__main__.py +55 -0
  3. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/bash_service.py +2 -1
  4. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/config.py +9 -0
  5. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/conversation_router.py +3 -15
  6. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/conversation_router_acp.py +37 -10
  7. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/conversation_service.py +144 -125
  8. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/docker/Dockerfile +99 -45
  9. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/event_service.py +123 -102
  10. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/models.py +13 -23
  11. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/persistence/__init__.py +2 -0
  12. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/persistence/models.py +40 -19
  13. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/persistence/store.py +1 -1
  14. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/pub_sub.py +28 -4
  15. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/skills_service.py +4 -7
  16. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/sockets.py +36 -7
  17. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands_agent_server.egg-info/PKG-INFO +1 -1
  18. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/pyproject.toml +1 -1
  19. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/__init__.py +0 -0
  20. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/_secrets_exposure.py +0 -0
  21. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/api.py +0 -0
  22. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/auth_router.py +0 -0
  23. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/bash_router.py +0 -0
  24. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/cloud_proxy_router.py +0 -0
  25. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/conversation_lease.py +0 -0
  26. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/dependencies.py +0 -0
  27. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/desktop_router.py +0 -0
  28. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/desktop_service.py +0 -0
  29. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/docker/build.py +0 -0
  30. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/docker/wallpaper.svg +0 -0
  31. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/env_parser.py +0 -0
  32. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/event_router.py +0 -0
  33. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/file_router.py +0 -0
  34. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/git_router.py +0 -0
  35. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/hooks_router.py +0 -0
  36. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/hooks_service.py +0 -0
  37. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/llm_router.py +0 -0
  38. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/logging_config.py +0 -0
  39. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/middleware.py +0 -0
  40. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/openapi.py +0 -0
  41. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/profiles_router.py +0 -0
  42. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/py.typed +0 -0
  43. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/server_details_router.py +0 -0
  44. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/settings_router.py +0 -0
  45. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/skills_router.py +0 -0
  46. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/tool_preload_service.py +0 -0
  47. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/tool_router.py +0 -0
  48. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/utils.py +0 -0
  49. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
  50. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
  51. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/vscode_router.py +0 -0
  52. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/vscode_service.py +0 -0
  53. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands/agent_server/workspace_router.py +0 -0
  54. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands_agent_server.egg-info/SOURCES.txt +0 -0
  55. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
  56. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands_agent_server.egg-info/entry_points.txt +0 -0
  57. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands_agent_server.egg-info/requires.txt +0 -0
  58. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/openhands_agent_server.egg-info/top_level.txt +0 -0
  59. {openhands_agent_server-1.22.0 → openhands_agent_server-1.22.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-agent-server
3
- Version: 1.22.0
3
+ Version: 1.22.1
4
4
  Summary: OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent
5
5
  Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
6
  Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
@@ -18,6 +18,7 @@ logger = get_logger(__name__)
18
18
 
19
19
 
20
20
  _INTERNAL_SERVER_URL_ENV = "OH_INTERNAL_SERVER_URL"
21
+ _EXTRA_PYTHON_PATH_ENV = "OH_EXTRA_PYTHON_PATH"
21
22
 
22
23
 
23
24
  def _get_internal_server_url(host: str, port: int) -> str:
@@ -43,6 +44,46 @@ def _get_internal_server_url(host: str, port: int) -> str:
43
44
  return f"http://{resolved_host}:{port}"
44
45
 
45
46
 
47
+ def extend_python_path(extra_paths: str | None) -> None:
48
+ """Add directories to ``sys.path`` so ``importlib.import_module`` can find
49
+ external custom-tool modules — even when running from a PyInstaller binary.
50
+
51
+ Paths are read from *extra_paths* (``--extra-python-path`` CLI arg) **and**
52
+ the ``OH_EXTRA_PYTHON_PATH`` environment variable. Both use the
53
+ platform path separator (``':'`` on POSIX, ``';'`` on Windows).
54
+
55
+ Non-existent directories are skipped with a warning; duplicates and paths
56
+ already on ``sys.path`` are silently ignored.
57
+ """
58
+ raw_parts: list[str] = []
59
+ for source in (extra_paths, os.environ.get(_EXTRA_PYTHON_PATH_ENV)):
60
+ if source:
61
+ raw_parts.extend(source.split(os.pathsep))
62
+
63
+ added = 0
64
+ for part in raw_parts:
65
+ part = part.strip()
66
+ if not part:
67
+ continue
68
+ resolved = os.path.abspath(part)
69
+ if not os.path.isdir(resolved):
70
+ logger.warning(
71
+ "Ignoring non-existent --extra-python-path entry: %s", resolved
72
+ )
73
+ continue
74
+ if resolved not in sys.path:
75
+ sys.path.insert(0, resolved)
76
+ logger.info("Added to sys.path: %s", resolved)
77
+ added += 1
78
+
79
+ if added:
80
+ logger.info(
81
+ "Extended sys.path with %d director%s for custom tool imports",
82
+ added,
83
+ "y" if added == 1 else "ies",
84
+ )
85
+
86
+
46
87
  def preload_modules(modules_arg: str | None) -> None:
47
88
  """Import user-specified modules so their top-level side effects run.
48
89
 
@@ -171,6 +212,16 @@ def main() -> None:
171
212
  "(e.g. 'myapp.tools,myapp.plugins')"
172
213
  ),
173
214
  )
215
+ parser.add_argument(
216
+ "--extra-python-path",
217
+ type=str,
218
+ default=None,
219
+ help=(
220
+ "Additional directories to add to sys.path for custom tool imports "
221
+ f"('{os.pathsep}'-separated). Also reads from the "
222
+ f"{_EXTRA_PYTHON_PATH_ENV} environment variable."
223
+ ),
224
+ )
174
225
 
175
226
  args = parser.parse_args()
176
227
 
@@ -181,6 +232,10 @@ def main() -> None:
181
232
  else:
182
233
  sys.exit(1)
183
234
 
235
+ # Extend sys.path before importing user modules so external .py files
236
+ # are reachable — critical for PyInstaller binary builds.
237
+ extend_python_path(args.extra_python_path)
238
+
184
239
  # Import user modules after early-exit checks
185
240
  preload_modules(args.import_modules)
186
241
 
@@ -32,7 +32,8 @@ class BashEventService:
32
32
 
33
33
  bash_events_dir: Path = field()
34
34
  _pub_sub: PubSub[BashEventBase] = field(
35
- default_factory=lambda: PubSub[BashEventBase](), init=False
35
+ default_factory=lambda: PubSub[BashEventBase](max_subscribers=50),
36
+ init=False,
36
37
  )
37
38
 
38
39
  def _ensure_bash_events_dir(self) -> None:
@@ -174,6 +174,15 @@ class Config(BaseModel):
174
174
  default=True,
175
175
  description="Whether to preload tools",
176
176
  )
177
+ max_concurrent_runs: int = Field(
178
+ default=10,
179
+ ge=1,
180
+ description=(
181
+ "Maximum number of conversations that can execute agent steps "
182
+ "concurrently. Controls the size of the dedicated thread pool "
183
+ "used for conversation.run() calls."
184
+ ),
185
+ )
177
186
  secret_key: SecretStr | None = Field(
178
187
  default_factory=_default_secret_key,
179
188
  description=(
@@ -19,10 +19,7 @@ from openhands.agent_server._secrets_exposure import (
19
19
  decrypt_incoming_llm_secrets,
20
20
  get_cipher,
21
21
  )
22
- from openhands.agent_server.conversation_service import (
23
- ConversationContractMismatchError,
24
- ConversationService,
25
- )
22
+ from openhands.agent_server.conversation_service import ConversationService
26
23
  from openhands.agent_server.dependencies import get_conversation_service
27
24
  from openhands.agent_server.models import (
28
25
  AgentResponseResult,
@@ -162,10 +159,7 @@ async def batch_get_conversations(
162
159
  # Write Methods
163
160
 
164
161
 
165
- @conversation_router.post(
166
- "",
167
- responses={409: {"description": "Conversation contract mismatch"}},
168
- )
162
+ @conversation_router.post("")
169
163
  async def start_conversation(
170
164
  request: Annotated[
171
165
  StartConversationRequest, Body(examples=START_CONVERSATION_EXAMPLES)
@@ -174,13 +168,7 @@ async def start_conversation(
174
168
  conversation_service: ConversationService = Depends(get_conversation_service),
175
169
  ) -> ConversationInfo:
176
170
  """Start a conversation in the local environment."""
177
- try:
178
- info, is_new = await conversation_service.start_conversation(request)
179
- except ConversationContractMismatchError as e:
180
- raise HTTPException(
181
- status_code=status.HTTP_409_CONFLICT,
182
- detail=str(e),
183
- ) from e
171
+ info, is_new = await conversation_service.start_conversation(request)
184
172
  response.status_code = status.HTTP_201_CREATED if is_new else status.HTTP_200_OK
185
173
  return info
186
174
 
@@ -1,5 +1,10 @@
1
1
  """ACP-capable conversation routes for the schema-sensitive endpoints."""
2
2
 
3
+ # Deprecated REST contract: all /api/acp/conversations routes were deprecated
4
+ # in v1.22.0 and are scheduled for removal in v1.27.0. The standard
5
+ # FastAPI/OpenAPI deprecation marker for routes is ``deprecated=True`` on each
6
+ # route decorator; keep matching docstring notices for CI deprecation checks.
7
+
3
8
  from typing import Annotated
4
9
  from uuid import UUID
5
10
 
@@ -53,7 +58,7 @@ START_ACP_CONVERSATION_EXAMPLES = [
53
58
  ]
54
59
 
55
60
 
56
- @conversation_router_acp.get("/search")
61
+ @conversation_router_acp.get("/search", deprecated=True)
57
62
  async def search_acp_conversations(
58
63
  page_id: Annotated[
59
64
  str | None,
@@ -73,7 +78,11 @@ async def search_acp_conversations(
73
78
  ] = ConversationSortOrder.CREATED_AT_DESC,
74
79
  conversation_service: ConversationService = Depends(get_conversation_service),
75
80
  ) -> ACPConversationPage:
76
- """Search conversations using the ACP-capable contract."""
81
+ """Search conversations using the ACP-capable contract.
82
+
83
+ Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
84
+ Use ``/api/conversations/search`` instead.
85
+ """
77
86
  assert limit > 0
78
87
  assert limit <= 100
79
88
  return await conversation_service.search_acp_conversations(
@@ -81,7 +90,7 @@ async def search_acp_conversations(
81
90
  )
82
91
 
83
92
 
84
- @conversation_router_acp.get("/count")
93
+ @conversation_router_acp.get("/count", deprecated=True)
85
94
  async def count_acp_conversations(
86
95
  status: Annotated[
87
96
  ConversationExecutionStatus | None,
@@ -89,36 +98,49 @@ async def count_acp_conversations(
89
98
  ] = None,
90
99
  conversation_service: ConversationService = Depends(get_conversation_service),
91
100
  ) -> int:
92
- """Count conversations using the ACP-capable contract."""
93
- return await conversation_service.count_acp_conversations(status)
101
+ """Count conversations using the ACP-capable contract.
102
+
103
+ Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
104
+ Use ``/api/conversations/count`` instead.
105
+ """
106
+ return await conversation_service.count_conversations(status)
94
107
 
95
108
 
96
109
  @conversation_router_acp.get(
97
110
  "/{conversation_id}",
98
111
  responses={404: {"description": "Item not found"}},
112
+ deprecated=True,
99
113
  )
100
114
  async def get_acp_conversation(
101
115
  conversation_id: UUID,
102
116
  conversation_service: ConversationService = Depends(get_conversation_service),
103
117
  ) -> ACPConversationInfo:
104
- """Get a conversation using the ACP-capable contract."""
118
+ """Get a conversation using the ACP-capable contract.
119
+
120
+ Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
121
+ Use ``/api/conversations/{conversation_id}`` instead.
122
+ """
105
123
  conversation = await conversation_service.get_acp_conversation(conversation_id)
106
124
  if conversation is None:
107
125
  raise HTTPException(status.HTTP_404_NOT_FOUND)
108
126
  return conversation
109
127
 
110
128
 
111
- @conversation_router_acp.get("")
129
+ @conversation_router_acp.get("", deprecated=True)
112
130
  async def batch_get_acp_conversations(
113
131
  ids: Annotated[list[UUID], Query()],
114
132
  conversation_service: ConversationService = Depends(get_conversation_service),
115
133
  ) -> list[ACPConversationInfo | None]:
116
- """Batch get conversations using the ACP-capable contract."""
134
+ """Batch get conversations using the ACP-capable contract.
135
+
136
+ Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
137
+ Use ``/api/conversations`` instead.
138
+ """
117
139
  assert len(ids) < 100
118
140
  return await conversation_service.batch_get_acp_conversations(ids)
119
141
 
120
142
 
121
- @conversation_router_acp.post("")
143
+ @conversation_router_acp.post("", deprecated=True)
122
144
  async def start_acp_conversation(
123
145
  request: Annotated[
124
146
  StartACPConversationRequest,
@@ -127,7 +149,12 @@ async def start_acp_conversation(
127
149
  response: Response,
128
150
  conversation_service: ConversationService = Depends(get_conversation_service),
129
151
  ) -> ACPConversationInfo:
130
- """Start a conversation using the ACP-capable contract."""
152
+ """Start a conversation using the ACP-capable contract.
153
+
154
+ Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
155
+ Use ``/api/conversations`` instead; it now accepts ACP agents and
156
+ ``agent_settings`` payloads.
157
+ """
131
158
  info, is_new = await conversation_service.start_acp_conversation(request)
132
159
  response.status_code = status.HTTP_201_CREATED if is_new else status.HTTP_200_OK
133
160
  return info