openhands-agent-server 1.26.0__tar.gz → 1.27.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.
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/PKG-INFO +1 -1
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/api.py +12 -5
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/conversation_service.py +8 -2
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/persistence/models.py +45 -4
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/settings_router.py +17 -7
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands_agent_server.egg-info/SOURCES.txt +0 -2
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/pyproject.toml +1 -1
- openhands_agent_server-1.26.0/openhands/agent_server/conversation_router_acp.py +0 -185
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/cloud_proxy_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/config.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/docker/Dockerfile +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/event_service.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/persistence/store.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/profiles_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/skills_service.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/sockets.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/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.27.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
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/api.py
RENAMED
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import os
|
|
3
3
|
import tempfile
|
|
4
4
|
import traceback
|
|
5
|
+
import uuid
|
|
5
6
|
from collections.abc import AsyncIterator, Sequence
|
|
6
7
|
from contextlib import asynccontextmanager, suppress
|
|
7
8
|
from pathlib import Path
|
|
@@ -24,7 +25,6 @@ from openhands.agent_server.config import (
|
|
|
24
25
|
get_default_config,
|
|
25
26
|
)
|
|
26
27
|
from openhands.agent_server.conversation_router import conversation_router
|
|
27
|
-
from openhands.agent_server.conversation_router_acp import conversation_router_acp
|
|
28
28
|
from openhands.agent_server.conversation_service import (
|
|
29
29
|
get_default_conversation_service,
|
|
30
30
|
)
|
|
@@ -300,7 +300,6 @@ def _add_api_routes(app: FastAPI, config: Config) -> None:
|
|
|
300
300
|
api_router = APIRouter(prefix="/api", dependencies=dependencies)
|
|
301
301
|
api_router.include_router(event_router)
|
|
302
302
|
api_router.include_router(conversation_router)
|
|
303
|
-
api_router.include_router(conversation_router_acp)
|
|
304
303
|
api_router.include_router(tool_router)
|
|
305
304
|
api_router.include_router(bash_router)
|
|
306
305
|
api_router.include_router(git_router)
|
|
@@ -431,18 +430,24 @@ def _add_exception_handlers(api: FastAPI) -> None:
|
|
|
431
430
|
request: Request, exc: Exception
|
|
432
431
|
) -> JSONResponse:
|
|
433
432
|
"""Handle unhandled exceptions."""
|
|
433
|
+
# Correlation id that ties the 500 a caller receives to the server-side
|
|
434
|
+
# log line (with full traceback) for this failure, so an otherwise
|
|
435
|
+
# opaque 500 can be matched to its traceback in the server logs.
|
|
436
|
+
error_id = uuid.uuid4().hex
|
|
434
437
|
# Always log that we're in the exception handler for debugging
|
|
435
438
|
logger.debug(
|
|
436
|
-
"Exception handler called for %s %s with %s: %s",
|
|
439
|
+
"Exception handler called for %s %s with %s: %s [error_id=%s]",
|
|
437
440
|
request.method,
|
|
438
441
|
request.url.path,
|
|
439
442
|
type(exc).__name__,
|
|
440
443
|
str(exc),
|
|
444
|
+
error_id,
|
|
441
445
|
)
|
|
442
446
|
|
|
443
447
|
content = {
|
|
444
448
|
"detail": "Internal Server Error",
|
|
445
449
|
"exception": str(exc),
|
|
450
|
+
"error_id": error_id,
|
|
446
451
|
}
|
|
447
452
|
# In DEBUG mode, include stack trace in response
|
|
448
453
|
if DEBUG:
|
|
@@ -458,9 +463,10 @@ def _add_exception_handlers(api: FastAPI) -> None:
|
|
|
458
463
|
return await _http_exception_handler(request, http_exc)
|
|
459
464
|
# If no HTTPException found, treat as unhandled exception
|
|
460
465
|
logger.error(
|
|
461
|
-
"Unhandled ExceptionGroup on %s %s",
|
|
466
|
+
"Unhandled ExceptionGroup on %s %s [error_id=%s]",
|
|
462
467
|
request.method,
|
|
463
468
|
request.url.path,
|
|
469
|
+
error_id,
|
|
464
470
|
exc_info=(type(exc), exc, exc.__traceback__),
|
|
465
471
|
)
|
|
466
472
|
return JSONResponse(status_code=500, content=content)
|
|
@@ -468,9 +474,10 @@ def _add_exception_handlers(api: FastAPI) -> None:
|
|
|
468
474
|
# Logs full stack trace for any unhandled error that FastAPI would
|
|
469
475
|
# turn into a 500
|
|
470
476
|
logger.error(
|
|
471
|
-
"Unhandled exception on %s %s",
|
|
477
|
+
"Unhandled exception on %s %s [error_id=%s]",
|
|
472
478
|
request.method,
|
|
473
479
|
request.url.path,
|
|
480
|
+
error_id,
|
|
474
481
|
exc_info=(type(exc), exc, exc.__traceback__),
|
|
475
482
|
)
|
|
476
483
|
return JSONResponse(status_code=500, content=content)
|
|
@@ -1256,9 +1256,15 @@ class WebhookSubscriber(Subscriber):
|
|
|
1256
1256
|
if self.session_api_key:
|
|
1257
1257
|
headers["X-Session-API-Key"] = self.session_api_key
|
|
1258
1258
|
|
|
1259
|
-
# Convert events to serializable format
|
|
1259
|
+
# Convert events to a JSON-serializable format. mode="json" is required
|
|
1260
|
+
# so types like set and SecretStr become JSON-safe primitives; without
|
|
1261
|
+
# it httpx's encoder raises "Object of type set/SecretStr is not JSON
|
|
1262
|
+
# serializable", every retry fails identically, and the events are
|
|
1263
|
+
# dropped. (Mirrors ConversationWebhookSubscriber.post_conversation_info.)
|
|
1260
1264
|
event_data = [
|
|
1261
|
-
event.model_dump(
|
|
1265
|
+
event.model_dump(mode="json")
|
|
1266
|
+
if hasattr(event, "model_dump")
|
|
1267
|
+
else event.__dict__
|
|
1262
1268
|
for event in events_to_post
|
|
1263
1269
|
]
|
|
1264
1270
|
|
|
@@ -34,16 +34,23 @@ from openhands.sdk.utils.pydantic_secrets import serialize_secret, validate_secr
|
|
|
34
34
|
class SettingsUpdatePayload(TypedDict, total=False):
|
|
35
35
|
"""Typed payload for PersistedSettings.update() method.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
All three ``*_diff`` dicts are deep-merged via :func:`_deep_merge`: nested
|
|
38
38
|
objects merge recursively, and a ``None`` value *inside a nested map*
|
|
39
39
|
deletes that entry (the "unset" primitive) — e.g. send
|
|
40
40
|
``{"acp_env": {"NAME": None}}`` to drop one env-var without re-sending the
|
|
41
41
|
whole map. A ``None`` on a top-level *field* is not treated as delete; it
|
|
42
42
|
flows to validation as before.
|
|
43
|
+
|
|
44
|
+
``misc_settings_diff`` is deep-merged into the persisted ``misc_settings``
|
|
45
|
+
block. The agent-server treats ``misc_settings`` as opaque
|
|
46
|
+
frontend-owned data (it persists and merges, but does not interpret), so
|
|
47
|
+
any shape the client chooses is valid; lists are replaced wholesale by
|
|
48
|
+
the deep-merge.
|
|
43
49
|
"""
|
|
44
50
|
|
|
45
51
|
agent_settings_diff: dict[str, Any]
|
|
46
52
|
conversation_settings_diff: dict[str, Any]
|
|
53
|
+
misc_settings_diff: dict[str, Any]
|
|
47
54
|
active_profile: str | None
|
|
48
55
|
|
|
49
56
|
|
|
@@ -97,7 +104,7 @@ def _deep_merge(
|
|
|
97
104
|
return result
|
|
98
105
|
|
|
99
106
|
|
|
100
|
-
PERSISTED_SETTINGS_SCHEMA_VERSION =
|
|
107
|
+
PERSISTED_SETTINGS_SCHEMA_VERSION = 2
|
|
101
108
|
|
|
102
109
|
|
|
103
110
|
class PersistedSettings(BaseModel):
|
|
@@ -109,6 +116,12 @@ class PersistedSettings(BaseModel):
|
|
|
109
116
|
|
|
110
117
|
The ``active_profile`` field tracks which LLM profile was last activated,
|
|
111
118
|
allowing frontends to display which profile is currently in use.
|
|
119
|
+
|
|
120
|
+
The ``misc_settings`` field is an opaque dict the agent-server persists
|
|
121
|
+
on behalf of the frontend. The agent-server never reads its contents and
|
|
122
|
+
has no schema for it; clients are free to store any JSON-serializable
|
|
123
|
+
structure they need (e.g. app/UI preferences, analytics consent, git
|
|
124
|
+
identity used for in-conversation commits, etc.).
|
|
112
125
|
"""
|
|
113
126
|
|
|
114
127
|
schema_version: int = Field(
|
|
@@ -124,6 +137,14 @@ class PersistedSettings(BaseModel):
|
|
|
124
137
|
default=None,
|
|
125
138
|
description="Name of the currently active LLM profile.",
|
|
126
139
|
)
|
|
140
|
+
misc_settings: dict[str, Any] = Field(
|
|
141
|
+
default_factory=dict,
|
|
142
|
+
description=(
|
|
143
|
+
"Opaque dict the agent-server persists on behalf of the frontend. "
|
|
144
|
+
"Updated through misc_settings_diff (deep-merged); contents are "
|
|
145
|
+
"never read or validated by the agent-server."
|
|
146
|
+
),
|
|
147
|
+
)
|
|
127
148
|
|
|
128
149
|
model_config = ConfigDict(populate_by_name=True)
|
|
129
150
|
|
|
@@ -173,7 +194,7 @@ class PersistedSettings(BaseModel):
|
|
|
173
194
|
agent_update = payload.get("agent_settings_diff")
|
|
174
195
|
conv_update = payload.get("conversation_settings_diff")
|
|
175
196
|
|
|
176
|
-
# Phase 1: Validate
|
|
197
|
+
# Phase 1: Validate all updates before any mutations
|
|
177
198
|
new_agent: AgentSettingsConfig | None = None
|
|
178
199
|
new_conv: ConversationSettings | None = None
|
|
179
200
|
agent_merged: dict | None = None
|
|
@@ -232,11 +253,23 @@ class PersistedSettings(BaseModel):
|
|
|
232
253
|
f"Failed to update conversation settings: {type(e).__name__}"
|
|
233
254
|
) from None
|
|
234
255
|
|
|
256
|
+
# ``misc_settings`` is opaque: deep-merge without schema
|
|
257
|
+
# validation. The agent-server doesn't interpret what's inside,
|
|
258
|
+
# and ``misc_settings`` is not a secret container — the merged
|
|
259
|
+
# dict is therefore stored directly without the post-commit
|
|
260
|
+
# clear-down used by ``agent_settings`` / ``conversation_settings``.
|
|
261
|
+
misc_update = payload.get("misc_settings_diff")
|
|
262
|
+
new_misc: dict[str, Any] | None = None
|
|
263
|
+
if isinstance(misc_update, dict):
|
|
264
|
+
new_misc = _deep_merge(self.misc_settings, misc_update)
|
|
265
|
+
|
|
235
266
|
# Phase 2: Apply validated changes atomically
|
|
236
267
|
if new_agent is not None:
|
|
237
268
|
self.agent_settings = new_agent
|
|
238
269
|
if new_conv is not None:
|
|
239
270
|
self.conversation_settings = new_conv
|
|
271
|
+
if new_misc is not None:
|
|
272
|
+
self.misc_settings = new_misc
|
|
240
273
|
|
|
241
274
|
# Update active_profile if explicitly provided (including None to clear)
|
|
242
275
|
if "active_profile" in payload:
|
|
@@ -252,7 +285,14 @@ class PersistedSettings(BaseModel):
|
|
|
252
285
|
def from_persisted(
|
|
253
286
|
cls, data: Any, *, context: dict[str, Any] | None = None
|
|
254
287
|
) -> PersistedSettings:
|
|
255
|
-
"""Load persisted settings
|
|
288
|
+
"""Load persisted settings.
|
|
289
|
+
|
|
290
|
+
Schema-version history:
|
|
291
|
+
|
|
292
|
+
- **v1**: ``agent_settings`` + ``conversation_settings`` only.
|
|
293
|
+
Missing ``misc_settings`` defaults to an empty dict.
|
|
294
|
+
- **v2** (current): adds the opaque ``misc_settings`` container.
|
|
295
|
+
"""
|
|
256
296
|
if not isinstance(data, dict):
|
|
257
297
|
return cls.model_validate(data, context=context)
|
|
258
298
|
|
|
@@ -266,6 +306,7 @@ class PersistedSettings(BaseModel):
|
|
|
266
306
|
f"{version} is newer than supported version "
|
|
267
307
|
f"{PERSISTED_SETTINGS_SCHEMA_VERSION}"
|
|
268
308
|
)
|
|
309
|
+
|
|
269
310
|
payload["schema_version"] = PERSISTED_SETTINGS_SCHEMA_VERSION
|
|
270
311
|
return cls.model_validate(payload, context=context)
|
|
271
312
|
|
|
@@ -160,6 +160,7 @@ async def get_settings(request: Request) -> SettingsResponse:
|
|
|
160
160
|
mode="json"
|
|
161
161
|
),
|
|
162
162
|
llm_api_key_is_set=settings.llm_api_key_is_set,
|
|
163
|
+
misc_settings=settings.misc_settings,
|
|
163
164
|
)
|
|
164
165
|
|
|
165
166
|
|
|
@@ -169,11 +170,12 @@ async def update_settings(
|
|
|
169
170
|
) -> SettingsResponse:
|
|
170
171
|
"""Update settings with partial changes.
|
|
171
172
|
|
|
172
|
-
Accepts ``agent_settings_diff`` and/or
|
|
173
|
-
for incremental updates.
|
|
174
|
-
recursively, and a ``null`` value **inside a nested
|
|
175
|
-
entry** — the "unset" primitive that lets a client
|
|
176
|
-
key without round-tripping the whole map. To drop one
|
|
173
|
+
Accepts ``agent_settings_diff``, ``conversation_settings_diff``, and/or
|
|
174
|
+
``misc_settings_diff`` for incremental updates. All three are deep-merged;
|
|
175
|
+
nested objects merge recursively, and a ``null`` value **inside a nested
|
|
176
|
+
map deletes that entry** — the "unset" primitive that lets a client
|
|
177
|
+
remove a single map key without round-tripping the whole map. To drop one
|
|
178
|
+
ACP env-var::
|
|
177
179
|
|
|
178
180
|
PATCH /api/settings
|
|
179
181
|
{"agent_settings_diff": {"acp_env": {"STALE_KEY": null}}}
|
|
@@ -187,6 +189,11 @@ async def update_settings(
|
|
|
187
189
|
is **not** an unset — it flows to model validation as before, so it still
|
|
188
190
|
fails loudly rather than silently resetting the field to its default.
|
|
189
191
|
|
|
192
|
+
``misc_settings_diff`` is deep-merged into the persisted ``misc_settings``
|
|
193
|
+
block. The agent-server treats ``misc_settings`` as opaque frontend-owned
|
|
194
|
+
data: nested dicts are merged recursively, lists are replaced wholesale,
|
|
195
|
+
and the contents are never read or validated server-side.
|
|
196
|
+
|
|
190
197
|
Uses file locking to prevent concurrent updates from overwriting each other.
|
|
191
198
|
|
|
192
199
|
Raises:
|
|
@@ -201,8 +208,9 @@ async def update_settings(
|
|
|
201
208
|
raise HTTPException(
|
|
202
209
|
status_code=400,
|
|
203
210
|
detail=(
|
|
204
|
-
"At least one of agent_settings_diff
|
|
205
|
-
"conversation_settings_diff
|
|
211
|
+
"At least one of agent_settings_diff, "
|
|
212
|
+
"conversation_settings_diff, or misc_settings_diff "
|
|
213
|
+
"must be provided"
|
|
206
214
|
),
|
|
207
215
|
)
|
|
208
216
|
|
|
@@ -223,6 +231,7 @@ async def update_settings(
|
|
|
223
231
|
"conversation_settings_modified": (
|
|
224
232
|
"conversation_settings_diff" in update_data
|
|
225
233
|
),
|
|
234
|
+
"misc_settings_modified": "misc_settings_diff" in update_data,
|
|
226
235
|
},
|
|
227
236
|
)
|
|
228
237
|
except (ValueError, ValidationError):
|
|
@@ -256,6 +265,7 @@ async def update_settings(
|
|
|
256
265
|
agent_settings=settings.agent_settings.model_dump(mode="json"),
|
|
257
266
|
conversation_settings=settings.conversation_settings.model_dump(mode="json"),
|
|
258
267
|
llm_api_key_is_set=settings.llm_api_key_is_set,
|
|
268
|
+
misc_settings=settings.misc_settings,
|
|
259
269
|
)
|
|
260
270
|
|
|
261
271
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.27.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
|
|
@@ -10,7 +10,6 @@ pyproject.toml
|
|
|
10
10
|
./openhands/agent_server/config.py
|
|
11
11
|
./openhands/agent_server/conversation_lease.py
|
|
12
12
|
./openhands/agent_server/conversation_router.py
|
|
13
|
-
./openhands/agent_server/conversation_router_acp.py
|
|
14
13
|
./openhands/agent_server/conversation_service.py
|
|
15
14
|
./openhands/agent_server/dependencies.py
|
|
16
15
|
./openhands/agent_server/desktop_router.py
|
|
@@ -62,7 +61,6 @@ openhands/agent_server/cloud_proxy_router.py
|
|
|
62
61
|
openhands/agent_server/config.py
|
|
63
62
|
openhands/agent_server/conversation_lease.py
|
|
64
63
|
openhands/agent_server/conversation_router.py
|
|
65
|
-
openhands/agent_server/conversation_router_acp.py
|
|
66
64
|
openhands/agent_server/conversation_service.py
|
|
67
65
|
openhands/agent_server/dependencies.py
|
|
68
66
|
openhands/agent_server/desktop_router.py
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
"""ACP-capable conversation routes for the schema-sensitive endpoints."""
|
|
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
|
-
|
|
8
|
-
from typing import Annotated
|
|
9
|
-
from uuid import UUID
|
|
10
|
-
|
|
11
|
-
from fastapi import APIRouter, Body, Depends, HTTPException, Query, Response, status
|
|
12
|
-
from pydantic import SecretStr
|
|
13
|
-
|
|
14
|
-
from openhands.agent_server.conversation_service import ConversationService
|
|
15
|
-
from openhands.agent_server.dependencies import get_conversation_service
|
|
16
|
-
from openhands.agent_server.models import (
|
|
17
|
-
INCLUDE_SKILLS_PARAM_TITLE,
|
|
18
|
-
ACPConversationInfo,
|
|
19
|
-
ACPConversationPage,
|
|
20
|
-
ConversationSortOrder,
|
|
21
|
-
SendMessageRequest,
|
|
22
|
-
StartACPConversationRequest,
|
|
23
|
-
trim_conversation_response_skills,
|
|
24
|
-
)
|
|
25
|
-
from openhands.sdk import LLM, Agent, TextContent
|
|
26
|
-
from openhands.sdk.agent.acp_agent import ACPAgent
|
|
27
|
-
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
28
|
-
from openhands.sdk.workspace import LocalWorkspace
|
|
29
|
-
from openhands.tools.preset.default import get_default_tools
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
conversation_router_acp = APIRouter(
|
|
33
|
-
prefix="/acp/conversations",
|
|
34
|
-
tags=["ACP Conversations"],
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
START_ACP_CONVERSATION_EXAMPLES = [
|
|
38
|
-
StartACPConversationRequest(
|
|
39
|
-
agent=Agent(
|
|
40
|
-
llm=LLM(
|
|
41
|
-
usage_id="your-llm-service",
|
|
42
|
-
model="your-model-provider/your-model-name",
|
|
43
|
-
api_key=SecretStr("your-api-key-here"),
|
|
44
|
-
),
|
|
45
|
-
tools=get_default_tools(enable_browser=True),
|
|
46
|
-
),
|
|
47
|
-
workspace=LocalWorkspace(working_dir="workspace/project"),
|
|
48
|
-
initial_message=SendMessageRequest(
|
|
49
|
-
role="user", content=[TextContent(text="Flip a coin!")]
|
|
50
|
-
),
|
|
51
|
-
).model_dump(exclude_defaults=True, mode="json"),
|
|
52
|
-
StartACPConversationRequest(
|
|
53
|
-
agent=ACPAgent(acp_command=["npx", "-y", "claude-agent-acp"]),
|
|
54
|
-
workspace=LocalWorkspace(working_dir="workspace/project"),
|
|
55
|
-
initial_message=SendMessageRequest(
|
|
56
|
-
role="user",
|
|
57
|
-
content=[TextContent(text="Inspect the repository and summarize it.")],
|
|
58
|
-
),
|
|
59
|
-
).model_dump(exclude_defaults=True, mode="json"),
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@conversation_router_acp.get("/search", deprecated=True)
|
|
64
|
-
async def search_acp_conversations(
|
|
65
|
-
page_id: Annotated[
|
|
66
|
-
str | None,
|
|
67
|
-
Query(title="Optional next_page_id from the previously returned page"),
|
|
68
|
-
] = None,
|
|
69
|
-
limit: Annotated[
|
|
70
|
-
int,
|
|
71
|
-
Query(title="The max number of results in the page", gt=0, lte=100),
|
|
72
|
-
] = 100,
|
|
73
|
-
status: Annotated[
|
|
74
|
-
ConversationExecutionStatus | None,
|
|
75
|
-
Query(title="Optional filter by conversation execution status"),
|
|
76
|
-
] = None,
|
|
77
|
-
sort_order: Annotated[
|
|
78
|
-
ConversationSortOrder,
|
|
79
|
-
Query(title="Sort order for conversations"),
|
|
80
|
-
] = ConversationSortOrder.CREATED_AT_DESC,
|
|
81
|
-
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
82
|
-
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
83
|
-
) -> ACPConversationPage:
|
|
84
|
-
"""Search conversations using the ACP-capable contract.
|
|
85
|
-
|
|
86
|
-
Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
|
|
87
|
-
Use ``/api/conversations/search`` instead.
|
|
88
|
-
"""
|
|
89
|
-
assert limit > 0
|
|
90
|
-
assert limit <= 100
|
|
91
|
-
page = await conversation_service.search_acp_conversations(
|
|
92
|
-
page_id, limit, status, sort_order
|
|
93
|
-
)
|
|
94
|
-
if not include_skills:
|
|
95
|
-
page = page.model_copy(
|
|
96
|
-
update={
|
|
97
|
-
"items": [
|
|
98
|
-
trim_conversation_response_skills(item) for item in page.items
|
|
99
|
-
]
|
|
100
|
-
}
|
|
101
|
-
)
|
|
102
|
-
return page
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
@conversation_router_acp.get("/count", deprecated=True)
|
|
106
|
-
async def count_acp_conversations(
|
|
107
|
-
status: Annotated[
|
|
108
|
-
ConversationExecutionStatus | None,
|
|
109
|
-
Query(title="Optional filter by conversation execution status"),
|
|
110
|
-
] = None,
|
|
111
|
-
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
112
|
-
) -> int:
|
|
113
|
-
"""Count conversations using the ACP-capable contract.
|
|
114
|
-
|
|
115
|
-
Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
|
|
116
|
-
Use ``/api/conversations/count`` instead.
|
|
117
|
-
"""
|
|
118
|
-
return await conversation_service.count_conversations(status)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@conversation_router_acp.get(
|
|
122
|
-
"/{conversation_id}",
|
|
123
|
-
responses={404: {"description": "Item not found"}},
|
|
124
|
-
deprecated=True,
|
|
125
|
-
)
|
|
126
|
-
async def get_acp_conversation(
|
|
127
|
-
conversation_id: UUID,
|
|
128
|
-
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
129
|
-
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
130
|
-
) -> ACPConversationInfo:
|
|
131
|
-
"""Get a conversation using the ACP-capable contract.
|
|
132
|
-
|
|
133
|
-
Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
|
|
134
|
-
Use ``/api/conversations/{conversation_id}`` instead.
|
|
135
|
-
"""
|
|
136
|
-
conversation = await conversation_service.get_acp_conversation(conversation_id)
|
|
137
|
-
if conversation is None:
|
|
138
|
-
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
|
139
|
-
if not include_skills:
|
|
140
|
-
conversation = trim_conversation_response_skills(conversation)
|
|
141
|
-
return conversation
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
@conversation_router_acp.get("", deprecated=True)
|
|
145
|
-
async def batch_get_acp_conversations(
|
|
146
|
-
ids: Annotated[list[UUID], Query()],
|
|
147
|
-
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
148
|
-
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
149
|
-
) -> list[ACPConversationInfo | None]:
|
|
150
|
-
"""Batch get conversations using the ACP-capable contract.
|
|
151
|
-
|
|
152
|
-
Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
|
|
153
|
-
Use ``/api/conversations`` instead.
|
|
154
|
-
"""
|
|
155
|
-
assert len(ids) < 100
|
|
156
|
-
conversations = await conversation_service.batch_get_acp_conversations(ids)
|
|
157
|
-
if not include_skills:
|
|
158
|
-
return [
|
|
159
|
-
trim_conversation_response_skills(c) if c is not None else None
|
|
160
|
-
for c in conversations
|
|
161
|
-
]
|
|
162
|
-
return conversations
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@conversation_router_acp.post("", deprecated=True)
|
|
166
|
-
async def start_acp_conversation(
|
|
167
|
-
request: Annotated[
|
|
168
|
-
StartACPConversationRequest,
|
|
169
|
-
Body(examples=START_ACP_CONVERSATION_EXAMPLES),
|
|
170
|
-
],
|
|
171
|
-
response: Response,
|
|
172
|
-
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
173
|
-
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
174
|
-
) -> ACPConversationInfo:
|
|
175
|
-
"""Start a conversation using the ACP-capable contract.
|
|
176
|
-
|
|
177
|
-
Deprecated since v1.22.0 and scheduled for removal in v1.27.0.
|
|
178
|
-
Use ``/api/conversations`` instead; it now accepts ACP agents and
|
|
179
|
-
``agent_settings`` payloads.
|
|
180
|
-
"""
|
|
181
|
-
info, is_new = await conversation_service.start_acp_conversation(request)
|
|
182
|
-
response.status_code = status.HTTP_201_CREATED if is_new else status.HTTP_200_OK
|
|
183
|
-
if not include_skills:
|
|
184
|
-
info = trim_conversation_response_skills(info)
|
|
185
|
-
return info
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/__init__.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/config.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
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/env_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/git_router.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/llm_router.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/mcp_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/middleware.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/models.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/openapi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/pub_sub.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/openhands/agent_server/sockets.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.26.0 → openhands_agent_server-1.27.1}/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
|
|
File without changes
|