openhands-agent-server 1.47.0__tar.gz → 1.49.0__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.47.0 → openhands_agent_server-1.49.0}/PKG-INFO +2 -2
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/api.py +28 -15
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/bash_router.py +14 -3
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/bash_service.py +18 -2
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/config.py +7 -0
- openhands_agent_server-1.49.0/openhands/agent_server/conversation_registry.py +74 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/conversation_router.py +29 -3
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/conversation_service.py +122 -19
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/dependencies.py +9 -1
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/docker/Dockerfile +114 -33
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/docker/build.py +8 -10
- openhands_agent_server-1.49.0/openhands/agent_server/docker_runtime/__init__.py +1 -0
- openhands_agent_server-1.49.0/openhands/agent_server/docker_runtime/mediation.py +112 -0
- openhands_agent_server-1.49.0/openhands/agent_server/docker_runtime/provisioning.py +163 -0
- openhands_agent_server-1.49.0/openhands/agent_server/docker_runtime/proxy.py +253 -0
- openhands_agent_server-1.49.0/openhands/agent_server/docker_runtime/registry.py +319 -0
- openhands_agent_server-1.49.0/openhands/agent_server/docker_runtime/routers.py +345 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/event_router.py +1 -3
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/event_service.py +11 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/file_router.py +27 -9
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/init_router.py +14 -1
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/mcp_router.py +14 -42
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/models.py +25 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/persistence/models.py +7 -2
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/profiles_router.py +1 -1
- openhands_agent_server-1.49.0/openhands/agent_server/runtime_router.py +105 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/server_details_router.py +15 -4
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/settings_router.py +16 -2
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/sockets.py +4 -3
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands_agent_server.egg-info/PKG-INFO +2 -2
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands_agent_server.egg-info/SOURCES.txt +16 -2
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands_agent_server.egg-info/requires.txt +1 -1
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/pyproject.toml +3 -3
- openhands_agent_server-1.47.0/openhands/agent_server/desktop_router.py +0 -28
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/_secret_redaction.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/agent_profiles_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/canvas_extensions/__init__.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/canvas_extensions/installed.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/canvas_extensions/manifest.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/canvas_extensions_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/credential_binding.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/mcp_oauth_store.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/openai/models.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/openai/router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/openai/service.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/persistence/store.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/plugins_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/plugins_service.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/provider_connections_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/session_protocol.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/session_socket.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/skills_service.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/__init__.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/factory.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/http_exporter.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/models.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/policy.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/posthog_exporter.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/sanitizer.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/service.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/sink.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry/subscriber.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/telemetry_types.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.49.0
|
|
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
|
|
@@ -15,7 +15,7 @@ Requires-Dist: openhands-sdk
|
|
|
15
15
|
Requires-Dist: openai<3,>=2.33.0
|
|
16
16
|
Requires-Dist: pydantic>=2
|
|
17
17
|
Requires-Dist: sqlalchemy>=2
|
|
18
|
-
Requires-Dist: uvicorn>=0.
|
|
18
|
+
Requires-Dist: uvicorn>=0.52.4
|
|
19
19
|
Requires-Dist: websockets>=12
|
|
20
20
|
Requires-Dist: wsproto>=1.2.0
|
|
21
21
|
Provides-Extra: posthog
|
{openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/api.py
RENAMED
|
@@ -26,7 +26,13 @@ from openhands.agent_server.config import (
|
|
|
26
26
|
Config,
|
|
27
27
|
get_default_config,
|
|
28
28
|
)
|
|
29
|
-
from openhands.agent_server.
|
|
29
|
+
from openhands.agent_server.conversation_registry import (
|
|
30
|
+
create_conversation_registry,
|
|
31
|
+
)
|
|
32
|
+
from openhands.agent_server.conversation_router import (
|
|
33
|
+
conversation_catalog_router,
|
|
34
|
+
conversation_router,
|
|
35
|
+
)
|
|
30
36
|
from openhands.agent_server.conversation_service import (
|
|
31
37
|
CredentialBindingActivationRequired,
|
|
32
38
|
get_default_conversation_service,
|
|
@@ -38,9 +44,7 @@ from openhands.agent_server.dependencies import (
|
|
|
38
44
|
check_session_api_key,
|
|
39
45
|
check_workspace_session,
|
|
40
46
|
)
|
|
41
|
-
from openhands.agent_server.
|
|
42
|
-
from openhands.agent_server.event_router import event_router
|
|
43
|
-
from openhands.agent_server.file_router import file_router
|
|
47
|
+
from openhands.agent_server.file_router import file_discovery_router, file_router
|
|
44
48
|
from openhands.agent_server.git_router import git_router
|
|
45
49
|
from openhands.agent_server.hooks_router import hooks_router
|
|
46
50
|
from openhands.agent_server.init_router import (
|
|
@@ -65,10 +69,8 @@ from openhands.agent_server.server_details_router import (
|
|
|
65
69
|
mark_initialization_complete,
|
|
66
70
|
server_details_router,
|
|
67
71
|
)
|
|
68
|
-
from openhands.agent_server.session_socket import session_router
|
|
69
72
|
from openhands.agent_server.settings_router import settings_router
|
|
70
73
|
from openhands.agent_server.skills_router import skills_router
|
|
71
|
-
from openhands.agent_server.sockets import sockets_router
|
|
72
74
|
from openhands.agent_server.sub_agents_router import sub_agents_router
|
|
73
75
|
from openhands.agent_server.telemetry import (
|
|
74
76
|
build_telemetry_sink,
|
|
@@ -91,7 +93,6 @@ from openhands.agent_server.tool_preload_service import get_tool_preload_service
|
|
|
91
93
|
from openhands.agent_server.tool_router import tool_router
|
|
92
94
|
from openhands.agent_server.vscode_router import vscode_router
|
|
93
95
|
from openhands.agent_server.vscode_service import get_vscode_service
|
|
94
|
-
from openhands.agent_server.workspace_router import workspace_router
|
|
95
96
|
from openhands.agent_server.workspaces_router import workspaces_router
|
|
96
97
|
from openhands.sdk.logger import DEBUG, get_logger
|
|
97
98
|
from openhands.sdk.utils.redact import sanitize_dict
|
|
@@ -159,6 +160,10 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
159
160
|
|
|
160
161
|
config: Config = api.state.config
|
|
161
162
|
deferred = config.deferred_init
|
|
163
|
+
conversation_registry = getattr(
|
|
164
|
+
api.state, "conversation_registry", None
|
|
165
|
+
) or create_conversation_registry(config)
|
|
166
|
+
api.state.conversation_registry = conversation_registry
|
|
162
167
|
|
|
163
168
|
# Deferred pods boot with telemetry disabled and are rebuilt by
|
|
164
169
|
# InitService, so they emit `server_started` there instead.
|
|
@@ -253,6 +258,11 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
253
258
|
bash_svc = get_default_bash_event_service()
|
|
254
259
|
api.state.bash_event_service = bash_svc
|
|
255
260
|
|
|
261
|
+
conversation_registry.configure_service(service)
|
|
262
|
+
# Runtime cleanup must precede external-catalog recovery so stale
|
|
263
|
+
# runtime owners cannot lose their expired leases to the outer service.
|
|
264
|
+
await conversation_registry.start()
|
|
265
|
+
|
|
256
266
|
async with service:
|
|
257
267
|
api.state.conversation_service = service
|
|
258
268
|
|
|
@@ -272,6 +282,7 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
272
282
|
try:
|
|
273
283
|
yield
|
|
274
284
|
finally:
|
|
285
|
+
await conversation_registry.shutdown()
|
|
275
286
|
if retention_task is not None:
|
|
276
287
|
retention_task.cancel()
|
|
277
288
|
with suppress(asyncio.CancelledError):
|
|
@@ -387,6 +398,7 @@ def _find_http_exception(exc: BaseExceptionGroup) -> HTTPException | None:
|
|
|
387
398
|
|
|
388
399
|
def _add_api_routes(app: FastAPI) -> None:
|
|
389
400
|
"""Add all API routes to the FastAPI application."""
|
|
401
|
+
conversation_registry = app.state.conversation_registry
|
|
390
402
|
app.include_router(server_details_router)
|
|
391
403
|
|
|
392
404
|
# The /api/init endpoint bypasses both the session-key auth and the
|
|
@@ -411,7 +423,11 @@ def _add_api_routes(app: FastAPI) -> None:
|
|
|
411
423
|
]
|
|
412
424
|
|
|
413
425
|
api_router = APIRouter(prefix="/api", dependencies=dependencies)
|
|
414
|
-
api_router.include_router(
|
|
426
|
+
api_router.include_router(file_discovery_router)
|
|
427
|
+
# Collection routes must precede runtime catch-alls such as
|
|
428
|
+
# ``/conversations/{conversation_id}``.
|
|
429
|
+
api_router.include_router(conversation_catalog_router)
|
|
430
|
+
conversation_registry.add_execution_routes(api_router)
|
|
415
431
|
api_router.include_router(conversation_router)
|
|
416
432
|
api_router.include_router(credential_binding_router)
|
|
417
433
|
api_router.include_router(tool_router)
|
|
@@ -419,7 +435,6 @@ def _add_api_routes(app: FastAPI) -> None:
|
|
|
419
435
|
api_router.include_router(git_router)
|
|
420
436
|
api_router.include_router(file_router)
|
|
421
437
|
api_router.include_router(vscode_router)
|
|
422
|
-
api_router.include_router(desktop_router)
|
|
423
438
|
api_router.include_router(skills_router)
|
|
424
439
|
api_router.include_router(sub_agents_router)
|
|
425
440
|
api_router.include_router(plugins_router)
|
|
@@ -435,8 +450,6 @@ def _add_api_routes(app: FastAPI) -> None:
|
|
|
435
450
|
# /api/auth/* mints workspace cookies and requires the header to bootstrap,
|
|
436
451
|
# so it lives under the header-only auth group.
|
|
437
452
|
api_router.include_router(auth_router)
|
|
438
|
-
app.include_router(api_router)
|
|
439
|
-
|
|
440
453
|
app.include_router(openai_router, dependencies=[Depends(check_openai_api_key)])
|
|
441
454
|
|
|
442
455
|
# Workspace static-file routes get their own auth group that accepts
|
|
@@ -447,12 +460,11 @@ def _add_api_routes(app: FastAPI) -> None:
|
|
|
447
460
|
workspace_api_router = APIRouter(
|
|
448
461
|
prefix="/api", dependencies=[Depends(check_workspace_session)]
|
|
449
462
|
)
|
|
450
|
-
workspace_api_router.include_router(workspace_router)
|
|
463
|
+
workspace_api_router.include_router(conversation_registry.workspace_router)
|
|
451
464
|
app.include_router(workspace_api_router)
|
|
465
|
+
app.include_router(api_router)
|
|
452
466
|
|
|
453
|
-
app.include_router(sockets_router)
|
|
454
|
-
|
|
455
|
-
app.include_router(session_router)
|
|
467
|
+
app.include_router(conversation_registry.sockets_router)
|
|
456
468
|
|
|
457
469
|
|
|
458
470
|
def _setup_static_files(app: FastAPI, config: Config) -> None:
|
|
@@ -680,6 +692,7 @@ def create_app(config: Config | None = None) -> FastAPI:
|
|
|
680
692
|
config = get_default_config()
|
|
681
693
|
app = _create_fastapi_instance(config)
|
|
682
694
|
app.state.config = config
|
|
695
|
+
app.state.conversation_registry = create_conversation_registry(config)
|
|
683
696
|
|
|
684
697
|
_add_api_routes(app)
|
|
685
698
|
_setup_static_files(app, config)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import Annotated, Literal, cast
|
|
6
7
|
from uuid import UUID
|
|
7
8
|
|
|
@@ -52,13 +53,11 @@ async def search_bash_events(
|
|
|
52
53
|
] = None,
|
|
53
54
|
limit: Annotated[
|
|
54
55
|
int,
|
|
55
|
-
Query(title="The max number of results in the page", gt=0,
|
|
56
|
+
Query(title="The max number of results in the page", gt=0, le=100),
|
|
56
57
|
] = 100,
|
|
57
58
|
bash_event_service: BashEventService = Depends(get_bash_event_service),
|
|
58
59
|
) -> BashEventPage:
|
|
59
60
|
"""Search / List bash event events"""
|
|
60
|
-
assert limit > 0
|
|
61
|
-
assert limit <= 100
|
|
62
61
|
|
|
63
62
|
return await bash_event_service.search_bash_events(
|
|
64
63
|
kind__eq=kind__eq,
|
|
@@ -104,6 +103,7 @@ async def start_bash_command(
|
|
|
104
103
|
) -> BashCommand:
|
|
105
104
|
"""Execute a bash command in the background"""
|
|
106
105
|
update_last_execution_time()
|
|
106
|
+
_validate_cwd(request, bash_event_service)
|
|
107
107
|
command, _ = await bash_event_service.start_bash_command(request)
|
|
108
108
|
return command
|
|
109
109
|
|
|
@@ -115,6 +115,7 @@ async def execute_bash_command(
|
|
|
115
115
|
) -> BashOutput:
|
|
116
116
|
"""Execute a bash command and wait for a result"""
|
|
117
117
|
update_last_execution_time()
|
|
118
|
+
_validate_cwd(request, bash_event_service)
|
|
118
119
|
command, task = await bash_event_service.start_bash_command(request)
|
|
119
120
|
await task
|
|
120
121
|
page = await bash_event_service.search_bash_events(command_id__eq=command.id)
|
|
@@ -129,3 +130,13 @@ async def clear_all_bash_events(
|
|
|
129
130
|
"""Clear all bash events from storage"""
|
|
130
131
|
count = await bash_event_service.clear_all_events()
|
|
131
132
|
return {"cleared_count": count}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _validate_cwd(request: ExecuteBashRequest, service: BashEventService) -> None:
|
|
136
|
+
if service.default_cwd is not None and request.cwd is not None:
|
|
137
|
+
if (
|
|
138
|
+
not Path(request.cwd)
|
|
139
|
+
.resolve()
|
|
140
|
+
.is_relative_to(Path(service.default_cwd).resolve())
|
|
141
|
+
):
|
|
142
|
+
raise HTTPException(422, "cwd must be inside the conversation workspace")
|
|
@@ -31,6 +31,9 @@ class BashEventService:
|
|
|
31
31
|
will not be visible to the agent."""
|
|
32
32
|
|
|
33
33
|
bash_events_dir: Path = field()
|
|
34
|
+
default_cwd: str | None = None
|
|
35
|
+
_tasks: set[asyncio.Task] = field(default_factory=set, init=False)
|
|
36
|
+
_closed: bool = field(default=False, init=False)
|
|
34
37
|
_pub_sub: PubSub[BashEventBase] = field(
|
|
35
38
|
default_factory=lambda: PubSub[BashEventBase](max_subscribers=50),
|
|
36
39
|
init=False,
|
|
@@ -255,13 +258,17 @@ class BashEventService:
|
|
|
255
258
|
self, request: ExecuteBashRequest
|
|
256
259
|
) -> tuple[BashCommand, asyncio.Task]:
|
|
257
260
|
"""Execute a bash command. The output will be published separately."""
|
|
258
|
-
|
|
261
|
+
if self._closed:
|
|
262
|
+
raise RuntimeError("Bash event service is closed")
|
|
263
|
+
cwd = request.cwd or self.default_cwd
|
|
264
|
+
command = BashCommand(**{**request.model_dump(), "cwd": cwd})
|
|
259
265
|
self._save_event_to_file(command)
|
|
260
266
|
await self._pub_sub(command)
|
|
261
267
|
|
|
262
268
|
# Execute the bash command in a background task
|
|
263
269
|
task = asyncio.create_task(self._execute_bash_command(command))
|
|
264
|
-
|
|
270
|
+
self._tasks.add(task)
|
|
271
|
+
task.add_done_callback(self._tasks.discard)
|
|
265
272
|
return command, task
|
|
266
273
|
|
|
267
274
|
async def _execute_bash_command(self, command: BashCommand) -> None:
|
|
@@ -347,6 +354,10 @@ class BashEventService:
|
|
|
347
354
|
timeout=command.timeout,
|
|
348
355
|
)
|
|
349
356
|
exit_code = process.returncode
|
|
357
|
+
except asyncio.CancelledError:
|
|
358
|
+
self._signal_process_group(process, signal.SIGKILL)
|
|
359
|
+
await process.wait()
|
|
360
|
+
raise
|
|
350
361
|
except TimeoutError:
|
|
351
362
|
# Send SIGTERM to the whole process group so user-installed
|
|
352
363
|
# cleanup traps can run, then escalate to SIGKILL if needed.
|
|
@@ -509,6 +520,11 @@ class BashEventService:
|
|
|
509
520
|
|
|
510
521
|
async def close(self):
|
|
511
522
|
"""Close the bash event service and clean up resources."""
|
|
523
|
+
self._closed = True
|
|
524
|
+
tasks = list(self._tasks)
|
|
525
|
+
for task in tasks:
|
|
526
|
+
task.cancel()
|
|
527
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
512
528
|
await self._pub_sub.close()
|
|
513
529
|
|
|
514
530
|
async def __aenter__(self):
|
{openhands_agent_server-1.47.0 → openhands_agent_server-1.49.0}/openhands/agent_server/config.py
RENAMED
|
@@ -346,6 +346,13 @@ class Config(BaseModel):
|
|
|
346
346
|
"The URL where this agent server instance is available externally"
|
|
347
347
|
),
|
|
348
348
|
)
|
|
349
|
+
conversation_runtime: Literal["local", "docker"] = "local"
|
|
350
|
+
conversation_image: str = "ghcr.io/openhands/agent-server:latest-python"
|
|
351
|
+
conversation_container_memory: str | None = "4g"
|
|
352
|
+
conversation_container_cpus: float | None = Field(default=2.0, gt=0)
|
|
353
|
+
conversation_container_pids_limit: int | None = Field(default=512, gt=0)
|
|
354
|
+
conversation_container_startup_timeout: float = Field(default=120, gt=0)
|
|
355
|
+
|
|
349
356
|
acp_skill_sourcing: ACPSkillSourcing = Field(
|
|
350
357
|
default="native",
|
|
351
358
|
description=(
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Select and manage the configured conversation runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter
|
|
8
|
+
|
|
9
|
+
from openhands.agent_server.config import Config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from openhands.agent_server.conversation_service import ConversationService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConversationRegistry:
|
|
17
|
+
"""Route and lifecycle adapter for host-local conversations."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: Config) -> None:
|
|
20
|
+
self.config = config
|
|
21
|
+
|
|
22
|
+
def configure_service(self, service: ConversationService) -> None:
|
|
23
|
+
"""Connect runtime-specific persistence to the shared catalog."""
|
|
24
|
+
|
|
25
|
+
async def start(self) -> None:
|
|
26
|
+
"""Start resources owned by this registry."""
|
|
27
|
+
|
|
28
|
+
async def shutdown(self) -> None:
|
|
29
|
+
"""Stop resources owned by this registry."""
|
|
30
|
+
|
|
31
|
+
def add_execution_routes(self, router: APIRouter) -> None:
|
|
32
|
+
from openhands.agent_server.event_router import event_router
|
|
33
|
+
from openhands.agent_server.runtime_router import create_runtime_router
|
|
34
|
+
|
|
35
|
+
router.include_router(create_runtime_router())
|
|
36
|
+
router.include_router(event_router)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def workspace_router(self) -> APIRouter:
|
|
40
|
+
from openhands.agent_server.workspace_router import workspace_router
|
|
41
|
+
|
|
42
|
+
return workspace_router
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def conversation_sockets_router(self) -> APIRouter:
|
|
46
|
+
from openhands.agent_server.sockets import conversation_sockets_router
|
|
47
|
+
|
|
48
|
+
return conversation_sockets_router
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def session_sockets_router(self) -> APIRouter:
|
|
52
|
+
from openhands.agent_server.session_socket import session_router
|
|
53
|
+
|
|
54
|
+
return session_router
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def sockets_router(self) -> APIRouter:
|
|
58
|
+
from openhands.agent_server.sockets import bash_sockets_router
|
|
59
|
+
|
|
60
|
+
router = APIRouter()
|
|
61
|
+
router.include_router(self.conversation_sockets_router)
|
|
62
|
+
router.include_router(self.session_sockets_router)
|
|
63
|
+
router.include_router(bash_sockets_router)
|
|
64
|
+
return router
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_conversation_registry(config: Config) -> ConversationRegistry:
|
|
68
|
+
if config.conversation_runtime == "docker":
|
|
69
|
+
from openhands.agent_server.docker_runtime.registry import (
|
|
70
|
+
DockerConversationRegistry,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return DockerConversationRegistry(config)
|
|
74
|
+
return ConversationRegistry(config)
|
|
@@ -31,6 +31,8 @@ from openhands.agent_server.models import (
|
|
|
31
31
|
AskAgentResponse,
|
|
32
32
|
ConversationInfo,
|
|
33
33
|
ConversationPage,
|
|
34
|
+
ConversationRuntimeInfo,
|
|
35
|
+
ConversationRuntimeStatus,
|
|
34
36
|
ConversationSortOrder,
|
|
35
37
|
ForkConversationRequest,
|
|
36
38
|
NavigateConversationRequest,
|
|
@@ -61,6 +63,7 @@ from openhands.sdk.workspace import LocalWorkspace
|
|
|
61
63
|
from openhands.tools.preset.default import get_default_tools
|
|
62
64
|
|
|
63
65
|
|
|
66
|
+
conversation_catalog_router = APIRouter(prefix="/conversations", tags=["Conversations"])
|
|
64
67
|
conversation_router = APIRouter(prefix="/conversations", tags=["Conversations"])
|
|
65
68
|
|
|
66
69
|
# Examples
|
|
@@ -86,6 +89,7 @@ START_CONVERSATION_EXAMPLES = [
|
|
|
86
89
|
# Read methods
|
|
87
90
|
|
|
88
91
|
|
|
92
|
+
@conversation_catalog_router.get("/search", include_in_schema=False)
|
|
89
93
|
@conversation_router.get("/search")
|
|
90
94
|
async def search_conversations(
|
|
91
95
|
page_id: Annotated[
|
|
@@ -94,7 +98,7 @@ async def search_conversations(
|
|
|
94
98
|
] = None,
|
|
95
99
|
limit: Annotated[
|
|
96
100
|
int,
|
|
97
|
-
Query(title="The max number of results in the page", gt=0,
|
|
101
|
+
Query(title="The max number of results in the page", gt=0, le=100),
|
|
98
102
|
] = 100,
|
|
99
103
|
status: Annotated[
|
|
100
104
|
ConversationExecutionStatus | None,
|
|
@@ -108,8 +112,6 @@ async def search_conversations(
|
|
|
108
112
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
109
113
|
) -> ConversationPage:
|
|
110
114
|
"""Search / List conversations"""
|
|
111
|
-
assert limit > 0
|
|
112
|
-
assert limit <= 100
|
|
113
115
|
page = await conversation_service.search_conversations(
|
|
114
116
|
page_id, limit, status, sort_order
|
|
115
117
|
)
|
|
@@ -128,6 +130,7 @@ async def search_conversations(
|
|
|
128
130
|
return page
|
|
129
131
|
|
|
130
132
|
|
|
133
|
+
@conversation_catalog_router.get("/count", include_in_schema=False)
|
|
131
134
|
@conversation_router.get("/count")
|
|
132
135
|
async def count_conversations(
|
|
133
136
|
status: Annotated[
|
|
@@ -158,6 +161,29 @@ async def get_conversation(
|
|
|
158
161
|
return conversation
|
|
159
162
|
|
|
160
163
|
|
|
164
|
+
@conversation_router.get("/{conversation_id}/runtime")
|
|
165
|
+
async def get_local_conversation_runtime(
|
|
166
|
+
conversation_id: UUID,
|
|
167
|
+
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
168
|
+
) -> ConversationRuntimeInfo:
|
|
169
|
+
"""Inspect the always-available in-process runtime."""
|
|
170
|
+
if await conversation_service.get_conversation(conversation_id) is None:
|
|
171
|
+
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
|
172
|
+
return ConversationRuntimeInfo(
|
|
173
|
+
runtime_status=ConversationRuntimeStatus.AVAILABLE,
|
|
174
|
+
can_resume=True,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@conversation_router.post("/{conversation_id}/runtime/reprovision")
|
|
179
|
+
async def reprovision_local_conversation_runtime(
|
|
180
|
+
conversation_id: UUID,
|
|
181
|
+
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
182
|
+
) -> ConversationRuntimeInfo:
|
|
183
|
+
"""Return local runtime state; local mode has no infrastructure to provision."""
|
|
184
|
+
return await get_local_conversation_runtime(conversation_id, conversation_service)
|
|
185
|
+
|
|
186
|
+
|
|
161
187
|
@conversation_router.get(
|
|
162
188
|
"/{conversation_id}/agent_final_response",
|
|
163
189
|
responses={404: {"description": "Conversation not found"}},
|