openhands-agent-server 1.7.2__tar.gz → 1.7.4__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.
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/PKG-INFO +1 -1
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/conversation_service.py +58 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/env_parser.py +34 -4
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/event_service.py +21 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/models.py +8 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/sockets.py +2 -2
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/pyproject.toml +1 -1
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/api.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/config.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/docker/Dockerfile +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands_agent_server.egg-info/SOURCES.txt +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/setup.cfg +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import importlib
|
|
2
3
|
import logging
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from pathlib import Path
|
|
@@ -208,6 +209,32 @@ class ConversationService:
|
|
|
208
209
|
)
|
|
209
210
|
return conversation_info, False
|
|
210
211
|
|
|
212
|
+
# Dynamically register tools from client's registry
|
|
213
|
+
if request.tool_module_qualnames:
|
|
214
|
+
import importlib
|
|
215
|
+
|
|
216
|
+
for tool_name, module_qualname in request.tool_module_qualnames.items():
|
|
217
|
+
try:
|
|
218
|
+
# Import the module to trigger tool auto-registration
|
|
219
|
+
importlib.import_module(module_qualname)
|
|
220
|
+
logger.debug(
|
|
221
|
+
f"Tool '{tool_name}' registered via module '{module_qualname}'"
|
|
222
|
+
)
|
|
223
|
+
except ImportError as e:
|
|
224
|
+
logger.warning(
|
|
225
|
+
f"Failed to import module '{module_qualname}' for tool "
|
|
226
|
+
f"'{tool_name}': {e}. Tool will not be available."
|
|
227
|
+
)
|
|
228
|
+
# Continue even if some tools fail to register
|
|
229
|
+
# The agent will fail gracefully if it tries to use unregistered
|
|
230
|
+
# tools
|
|
231
|
+
if request.tool_module_qualnames:
|
|
232
|
+
logger.info(
|
|
233
|
+
f"Dynamically registered {len(request.tool_module_qualnames)} "
|
|
234
|
+
f"tools for conversation {conversation_id}: "
|
|
235
|
+
f"{list(request.tool_module_qualnames.keys())}"
|
|
236
|
+
)
|
|
237
|
+
|
|
211
238
|
stored = StoredConversation(id=conversation_id, **request.model_dump())
|
|
212
239
|
event_service = await self._start_event_service(stored)
|
|
213
240
|
initial_message = request.initial_message
|
|
@@ -378,6 +405,34 @@ class ConversationService:
|
|
|
378
405
|
"cipher": self.cipher,
|
|
379
406
|
},
|
|
380
407
|
)
|
|
408
|
+
# Dynamically register tools when resuming persisted conversations
|
|
409
|
+
if stored.tool_module_qualnames:
|
|
410
|
+
for (
|
|
411
|
+
tool_name,
|
|
412
|
+
module_qualname,
|
|
413
|
+
) in stored.tool_module_qualnames.items():
|
|
414
|
+
try:
|
|
415
|
+
# Import the module to trigger tool auto-registration
|
|
416
|
+
importlib.import_module(module_qualname)
|
|
417
|
+
logger.debug(
|
|
418
|
+
f"Tool '{tool_name}' registered via module "
|
|
419
|
+
f"'{module_qualname}' when resuming conversation "
|
|
420
|
+
f"{stored.id}"
|
|
421
|
+
)
|
|
422
|
+
except ImportError as e:
|
|
423
|
+
logger.warning(
|
|
424
|
+
f"Failed to import module '{module_qualname}' for "
|
|
425
|
+
f"tool '{tool_name}' when resuming conversation "
|
|
426
|
+
f"{stored.id}: {e}. Tool will not be available."
|
|
427
|
+
)
|
|
428
|
+
# Continue even if some tools fail to register
|
|
429
|
+
if stored.tool_module_qualnames:
|
|
430
|
+
logger.info(
|
|
431
|
+
f"Dynamically registered "
|
|
432
|
+
f"{len(stored.tool_module_qualnames)} tools when "
|
|
433
|
+
f"resuming conversation {stored.id}: "
|
|
434
|
+
f"{list(stored.tool_module_qualnames.keys())}"
|
|
435
|
+
)
|
|
381
436
|
await self._start_event_service(stored)
|
|
382
437
|
except Exception:
|
|
383
438
|
logger.exception(
|
|
@@ -447,6 +502,9 @@ class ConversationService:
|
|
|
447
502
|
|
|
448
503
|
try:
|
|
449
504
|
await event_service.start()
|
|
505
|
+
# Save metadata immediately after successful start to ensure persistence
|
|
506
|
+
# even if the system is not shut down gracefully
|
|
507
|
+
await event_service.save_meta()
|
|
450
508
|
except Exception:
|
|
451
509
|
# Clean up the event service if startup fails
|
|
452
510
|
await event_service.close()
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/env_parser.py
RENAMED
|
@@ -17,7 +17,10 @@ from uuid import UUID
|
|
|
17
17
|
|
|
18
18
|
from pydantic import BaseModel, SecretStr, TypeAdapter
|
|
19
19
|
|
|
20
|
-
from openhands.sdk.utils.models import
|
|
20
|
+
from openhands.sdk.utils.models import (
|
|
21
|
+
DiscriminatedUnionMixin,
|
|
22
|
+
get_known_concrete_subclasses,
|
|
23
|
+
)
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
# Define Missing type
|
|
@@ -268,6 +271,26 @@ class UnionEnvParser(EnvParser):
|
|
|
268
271
|
output.write("\n")
|
|
269
272
|
|
|
270
273
|
|
|
274
|
+
@dataclass
|
|
275
|
+
class DiscriminatedUnionEnvParser(EnvParser):
|
|
276
|
+
parsers: dict[str, EnvParser]
|
|
277
|
+
|
|
278
|
+
def from_env(self, key: str) -> JsonType:
|
|
279
|
+
kind = os.environ.get(f"{key}_KIND", MISSING)
|
|
280
|
+
if kind is MISSING:
|
|
281
|
+
return MISSING
|
|
282
|
+
assert isinstance(kind, str)
|
|
283
|
+
parser = self.parsers[kind]
|
|
284
|
+
parser_result = parser.from_env(key)
|
|
285
|
+
assert isinstance(parser_result, dict)
|
|
286
|
+
parser_result["kind"] = kind
|
|
287
|
+
return parser_result
|
|
288
|
+
|
|
289
|
+
def to_env(self, key: str, value: Any, output: IO):
|
|
290
|
+
parser = self.parsers[value.kind]
|
|
291
|
+
parser.to_env(key, value, output)
|
|
292
|
+
|
|
293
|
+
|
|
271
294
|
@dataclass
|
|
272
295
|
class DelayedParser(EnvParser):
|
|
273
296
|
"""Delayed parser for circular dependencies"""
|
|
@@ -341,9 +364,16 @@ def get_env_parser(target_type: type, parsers: dict[type, EnvParser]) -> EnvPars
|
|
|
341
364
|
if issubclass(target_type, DiscriminatedUnionMixin) and (
|
|
342
365
|
inspect.isabstract(target_type) or ABC in target_type.__bases__
|
|
343
366
|
):
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
367
|
+
delayed = DelayedParser()
|
|
368
|
+
parsers[target_type] = delayed # Prevent circular dependency
|
|
369
|
+
sub_parsers = {
|
|
370
|
+
c.__name__: get_env_parser(c, parsers)
|
|
371
|
+
for c in get_known_concrete_subclasses(target_type)
|
|
372
|
+
}
|
|
373
|
+
parser = DiscriminatedUnionEnvParser(sub_parsers)
|
|
374
|
+
delayed.parser = parser
|
|
375
|
+
parsers[target_type] = parser
|
|
376
|
+
return parser
|
|
347
377
|
if issubclass(target_type, BaseModel): # type: ignore
|
|
348
378
|
delayed = DelayedParser()
|
|
349
379
|
parsers[target_type] = delayed # Prevent circular dependency
|
|
@@ -19,6 +19,7 @@ from openhands.sdk.conversation.state import (
|
|
|
19
19
|
ConversationExecutionStatus,
|
|
20
20
|
ConversationState,
|
|
21
21
|
)
|
|
22
|
+
from openhands.sdk.event import AgentErrorEvent
|
|
22
23
|
from openhands.sdk.event.conversation_state import ConversationStateUpdateEvent
|
|
23
24
|
from openhands.sdk.event.llm_completion_log import LLMCompletionLogEvent
|
|
24
25
|
from openhands.sdk.security.analyzer import SecurityAnalyzerBase
|
|
@@ -439,6 +440,26 @@ class EventService:
|
|
|
439
440
|
# Setup stats streaming for remote execution
|
|
440
441
|
self._setup_stats_streaming(self._conversation.agent)
|
|
441
442
|
|
|
443
|
+
# If the execution_status was "running" while serialized, then the
|
|
444
|
+
# conversation can't possibly be running - something is wrong
|
|
445
|
+
state = self._conversation.state
|
|
446
|
+
if state.execution_status == ConversationExecutionStatus.RUNNING:
|
|
447
|
+
state.execution_status = ConversationExecutionStatus.ERROR
|
|
448
|
+
# Add error event for the first unmatched action to inform the agent
|
|
449
|
+
unmatched_actions = ConversationState.get_unmatched_actions(state.events)
|
|
450
|
+
if unmatched_actions:
|
|
451
|
+
first_action = unmatched_actions[0]
|
|
452
|
+
error_event = AgentErrorEvent(
|
|
453
|
+
tool_name=first_action.tool_name,
|
|
454
|
+
tool_call_id=first_action.tool_call_id,
|
|
455
|
+
error=(
|
|
456
|
+
"A restart occurred while this tool was in progress. "
|
|
457
|
+
"This may indicate a fatal memory error or system crash. "
|
|
458
|
+
"The tool execution was interrupted and did not complete."
|
|
459
|
+
),
|
|
460
|
+
)
|
|
461
|
+
self._conversation._on_event(error_event)
|
|
462
|
+
|
|
442
463
|
# Publish initial state update
|
|
443
464
|
await self._publish_state_update()
|
|
444
465
|
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/models.py
RENAMED
|
@@ -98,6 +98,14 @@ class StartConversationRequest(BaseModel):
|
|
|
98
98
|
default_factory=dict,
|
|
99
99
|
description="Secrets available in the conversation",
|
|
100
100
|
)
|
|
101
|
+
tool_module_qualnames: dict[str, str] = Field(
|
|
102
|
+
default_factory=dict,
|
|
103
|
+
description=(
|
|
104
|
+
"Mapping of tool names to their module qualnames from the client's "
|
|
105
|
+
"registry. These modules will be dynamically imported on the server "
|
|
106
|
+
"to register the tools for this conversation."
|
|
107
|
+
),
|
|
108
|
+
)
|
|
101
109
|
|
|
102
110
|
|
|
103
111
|
class StoredConversation(StartConversationRequest):
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/sockets.py
RENAMED
|
@@ -139,7 +139,7 @@ async def bash_events_socket(
|
|
|
139
139
|
|
|
140
140
|
async def _send_event(event: Event, websocket: WebSocket):
|
|
141
141
|
try:
|
|
142
|
-
dumped = event.model_dump()
|
|
142
|
+
dumped = event.model_dump(mode="json")
|
|
143
143
|
await websocket.send_json(dumped)
|
|
144
144
|
except Exception:
|
|
145
145
|
logger.exception("error_sending_event:{event}", stack_info=True)
|
|
@@ -157,7 +157,7 @@ class _WebSocketSubscriber(Subscriber):
|
|
|
157
157
|
|
|
158
158
|
async def _send_bash_event(event: BashEventBase, websocket: WebSocket):
|
|
159
159
|
try:
|
|
160
|
-
dumped = event.model_dump()
|
|
160
|
+
dumped = event.model_dump(mode="json")
|
|
161
161
|
await websocket.send_json(dumped)
|
|
162
162
|
except Exception:
|
|
163
163
|
logger.exception("error_sending_event:{event}", stack_info=True)
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/__init__.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/bash_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/bash_service.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/docker/build.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/event_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/file_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/git_router.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/middleware.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/openapi.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/pub_sub.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/tool_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.7.2 → openhands_agent_server-1.7.4}/openhands/agent_server/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|