openhands-agent-server 1.22.0__tar.gz → 1.23.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.22.0 → openhands_agent_server-1.23.0}/PKG-INFO +1 -1
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/__main__.py +55 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/api.py +4 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/bash_service.py +2 -1
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/config.py +9 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_router.py +55 -16
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_router_acp.py +66 -14
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_service.py +163 -125
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/docker/Dockerfile +99 -45
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/event_service.py +179 -104
- openhands_agent_server-1.23.0/openhands/agent_server/mcp_router.py +225 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/models.py +63 -22
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/persistence/__init__.py +16 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/persistence/models.py +89 -19
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/persistence/store.py +108 -2
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/pub_sub.py +28 -4
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/server_details_router.py +10 -0
- openhands_agent_server-1.23.0/openhands/agent_server/skills_router.py +530 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/skills_service.py +302 -7
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/sockets.py +36 -7
- openhands_agent_server-1.23.0/openhands/agent_server/workspaces_router.py +244 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/SOURCES.txt +4 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/pyproject.toml +1 -1
- openhands_agent_server-1.22.0/openhands/agent_server/skills_router.py +0 -192
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/cloud_proxy_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/profiles_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/settings_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.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.23.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
|
{openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/__main__.py
RENAMED
|
@@ -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
|
|
{openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/api.py
RENAMED
|
@@ -38,6 +38,7 @@ from openhands.agent_server.file_router import file_router
|
|
|
38
38
|
from openhands.agent_server.git_router import git_router
|
|
39
39
|
from openhands.agent_server.hooks_router import hooks_router
|
|
40
40
|
from openhands.agent_server.llm_router import llm_router
|
|
41
|
+
from openhands.agent_server.mcp_router import mcp_router
|
|
41
42
|
from openhands.agent_server.middleware import LocalhostCORSMiddleware
|
|
42
43
|
from openhands.agent_server.profiles_router import profiles_router
|
|
43
44
|
from openhands.agent_server.server_details_router import (
|
|
@@ -53,6 +54,7 @@ from openhands.agent_server.tool_router import tool_router
|
|
|
53
54
|
from openhands.agent_server.vscode_router import vscode_router
|
|
54
55
|
from openhands.agent_server.vscode_service import get_vscode_service
|
|
55
56
|
from openhands.agent_server.workspace_router import workspace_router
|
|
57
|
+
from openhands.agent_server.workspaces_router import workspaces_router
|
|
56
58
|
from openhands.sdk.logger import DEBUG, get_logger
|
|
57
59
|
from openhands.sdk.utils.redact import sanitize_dict
|
|
58
60
|
from openhands.tools.terminal.constants import TMUX_SOCKET_NAME
|
|
@@ -288,7 +290,9 @@ def _add_api_routes(app: FastAPI, config: Config) -> None:
|
|
|
288
290
|
api_router.include_router(skills_router)
|
|
289
291
|
api_router.include_router(hooks_router)
|
|
290
292
|
api_router.include_router(llm_router)
|
|
293
|
+
api_router.include_router(mcp_router)
|
|
291
294
|
api_router.include_router(settings_router)
|
|
295
|
+
api_router.include_router(workspaces_router)
|
|
292
296
|
api_router.include_router(profiles_router)
|
|
293
297
|
api_router.include_router(cloud_proxy_router)
|
|
294
298
|
# /api/auth/* mints workspace cookies and requires the header to bootstrap,
|
|
@@ -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](),
|
|
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:
|
{openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/config.py
RENAMED
|
@@ -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,12 +19,10 @@ 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 (
|
|
25
|
+
INCLUDE_SKILLS_PARAM_TITLE,
|
|
28
26
|
AgentResponseResult,
|
|
29
27
|
AskAgentRequest,
|
|
30
28
|
AskAgentResponse,
|
|
@@ -39,6 +37,7 @@ from openhands.agent_server.models import (
|
|
|
39
37
|
Success,
|
|
40
38
|
UpdateConversationRequest,
|
|
41
39
|
UpdateSecretsRequest,
|
|
40
|
+
trim_conversation_response_skills,
|
|
42
41
|
)
|
|
43
42
|
from openhands.sdk import LLM, Agent, TextContent
|
|
44
43
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
@@ -89,14 +88,28 @@ async def search_conversations(
|
|
|
89
88
|
ConversationSortOrder,
|
|
90
89
|
Query(title="Sort order for conversations"),
|
|
91
90
|
] = ConversationSortOrder.CREATED_AT_DESC,
|
|
91
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
92
92
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
93
93
|
) -> ConversationPage:
|
|
94
94
|
"""Search / List conversations"""
|
|
95
95
|
assert limit > 0
|
|
96
96
|
assert limit <= 100
|
|
97
|
-
|
|
97
|
+
page = await conversation_service.search_conversations(
|
|
98
98
|
page_id, limit, status, sort_order
|
|
99
99
|
)
|
|
100
|
+
if not include_skills:
|
|
101
|
+
# ``model_copy`` rather than in-place mutation so we never
|
|
102
|
+
# write back into whatever the upstream service handed us
|
|
103
|
+
# (matters for services that cache their return value,
|
|
104
|
+
# including the ``AsyncMock`` used in route tests).
|
|
105
|
+
page = page.model_copy(
|
|
106
|
+
update={
|
|
107
|
+
"items": [
|
|
108
|
+
trim_conversation_response_skills(item) for item in page.items
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
return page
|
|
100
113
|
|
|
101
114
|
|
|
102
115
|
@conversation_router.get("/count")
|
|
@@ -117,12 +130,15 @@ async def count_conversations(
|
|
|
117
130
|
)
|
|
118
131
|
async def get_conversation(
|
|
119
132
|
conversation_id: UUID,
|
|
133
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
120
134
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
121
135
|
) -> ConversationInfo:
|
|
122
136
|
"""Given an id, get a conversation"""
|
|
123
137
|
conversation = await conversation_service.get_conversation(conversation_id)
|
|
124
138
|
if conversation is None:
|
|
125
139
|
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
|
140
|
+
if not include_skills:
|
|
141
|
+
conversation = trim_conversation_response_skills(conversation)
|
|
126
142
|
return conversation
|
|
127
143
|
|
|
128
144
|
|
|
@@ -150,38 +166,38 @@ async def get_conversation_agent_final_response(
|
|
|
150
166
|
@conversation_router.get("")
|
|
151
167
|
async def batch_get_conversations(
|
|
152
168
|
ids: Annotated[list[UUID], Query()],
|
|
169
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
153
170
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
154
171
|
) -> list[ConversationInfo | None]:
|
|
155
172
|
"""Get a batch of conversations given their ids, returning null for
|
|
156
173
|
any missing item"""
|
|
157
174
|
assert len(ids) < 100
|
|
158
175
|
conversations = await conversation_service.batch_get_conversations(ids)
|
|
176
|
+
if not include_skills:
|
|
177
|
+
return [
|
|
178
|
+
trim_conversation_response_skills(c) if c is not None else None
|
|
179
|
+
for c in conversations
|
|
180
|
+
]
|
|
159
181
|
return conversations
|
|
160
182
|
|
|
161
183
|
|
|
162
184
|
# Write Methods
|
|
163
185
|
|
|
164
186
|
|
|
165
|
-
@conversation_router.post(
|
|
166
|
-
"",
|
|
167
|
-
responses={409: {"description": "Conversation contract mismatch"}},
|
|
168
|
-
)
|
|
187
|
+
@conversation_router.post("")
|
|
169
188
|
async def start_conversation(
|
|
170
189
|
request: Annotated[
|
|
171
190
|
StartConversationRequest, Body(examples=START_CONVERSATION_EXAMPLES)
|
|
172
191
|
],
|
|
173
192
|
response: Response,
|
|
193
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
174
194
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
175
195
|
) -> ConversationInfo:
|
|
176
196
|
"""Start a conversation in the local environment."""
|
|
177
|
-
|
|
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
|
|
197
|
+
info, is_new = await conversation_service.start_conversation(request)
|
|
184
198
|
response.status_code = status.HTTP_201_CREATED if is_new else status.HTTP_200_OK
|
|
199
|
+
if not include_skills:
|
|
200
|
+
info = trim_conversation_response_skills(info)
|
|
185
201
|
return info
|
|
186
202
|
|
|
187
203
|
|
|
@@ -199,6 +215,26 @@ async def pause_conversation(
|
|
|
199
215
|
return Success()
|
|
200
216
|
|
|
201
217
|
|
|
218
|
+
@conversation_router.post(
|
|
219
|
+
"/{conversation_id}/interrupt",
|
|
220
|
+
responses={404: {"description": "Item not found"}},
|
|
221
|
+
)
|
|
222
|
+
async def interrupt_conversation(
|
|
223
|
+
conversation_id: UUID,
|
|
224
|
+
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
225
|
+
) -> Success:
|
|
226
|
+
"""Immediately interrupt a running conversation.
|
|
227
|
+
|
|
228
|
+
Unlike ``/pause``, which waits for the current LLM call to finish,
|
|
229
|
+
``/interrupt`` cancels the in-flight request so the effect is instant.
|
|
230
|
+
The conversation transitions to *paused* and can be resumed later.
|
|
231
|
+
"""
|
|
232
|
+
interrupted = await conversation_service.interrupt_conversation(conversation_id)
|
|
233
|
+
if not interrupted:
|
|
234
|
+
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
|
235
|
+
return Success()
|
|
236
|
+
|
|
237
|
+
|
|
202
238
|
@conversation_router.delete(
|
|
203
239
|
"/{conversation_id}", responses={404: {"description": "Item not found"}}
|
|
204
240
|
)
|
|
@@ -419,6 +455,7 @@ async def condense_conversation(
|
|
|
419
455
|
async def fork_conversation(
|
|
420
456
|
conversation_id: UUID,
|
|
421
457
|
request: Annotated[ForkConversationRequest, Body()] = ForkConversationRequest(), # noqa: B008
|
|
458
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
422
459
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
423
460
|
) -> ConversationInfo:
|
|
424
461
|
"""Fork a conversation, deep-copying its event history.
|
|
@@ -444,4 +481,6 @@ async def fork_conversation(
|
|
|
444
481
|
status.HTTP_404_NOT_FOUND,
|
|
445
482
|
detail="Source conversation not found",
|
|
446
483
|
)
|
|
484
|
+
if not include_skills:
|
|
485
|
+
info = trim_conversation_response_skills(info)
|
|
447
486
|
return info
|
|
@@ -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
|
|
|
@@ -9,11 +14,13 @@ from pydantic import SecretStr
|
|
|
9
14
|
from openhands.agent_server.conversation_service import ConversationService
|
|
10
15
|
from openhands.agent_server.dependencies import get_conversation_service
|
|
11
16
|
from openhands.agent_server.models import (
|
|
17
|
+
INCLUDE_SKILLS_PARAM_TITLE,
|
|
12
18
|
ACPConversationInfo,
|
|
13
19
|
ACPConversationPage,
|
|
14
20
|
ConversationSortOrder,
|
|
15
21
|
SendMessageRequest,
|
|
16
22
|
StartACPConversationRequest,
|
|
23
|
+
trim_conversation_response_skills,
|
|
17
24
|
)
|
|
18
25
|
from openhands.sdk import LLM, Agent, TextContent
|
|
19
26
|
from openhands.sdk.agent.acp_agent import ACPAgent
|
|
@@ -53,7 +60,7 @@ START_ACP_CONVERSATION_EXAMPLES = [
|
|
|
53
60
|
]
|
|
54
61
|
|
|
55
62
|
|
|
56
|
-
@conversation_router_acp.get("/search")
|
|
63
|
+
@conversation_router_acp.get("/search", deprecated=True)
|
|
57
64
|
async def search_acp_conversations(
|
|
58
65
|
page_id: Annotated[
|
|
59
66
|
str | None,
|
|
@@ -71,17 +78,31 @@ async def search_acp_conversations(
|
|
|
71
78
|
ConversationSortOrder,
|
|
72
79
|
Query(title="Sort order for conversations"),
|
|
73
80
|
] = ConversationSortOrder.CREATED_AT_DESC,
|
|
81
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
74
82
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
75
83
|
) -> ACPConversationPage:
|
|
76
|
-
"""Search conversations using the ACP-capable contract.
|
|
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
|
+
"""
|
|
77
89
|
assert limit > 0
|
|
78
90
|
assert limit <= 100
|
|
79
|
-
|
|
91
|
+
page = await conversation_service.search_acp_conversations(
|
|
80
92
|
page_id, limit, status, sort_order
|
|
81
93
|
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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)
|
|
85
106
|
async def count_acp_conversations(
|
|
86
107
|
status: Annotated[
|
|
87
108
|
ConversationExecutionStatus | None,
|
|
@@ -89,45 +110,76 @@ async def count_acp_conversations(
|
|
|
89
110
|
] = None,
|
|
90
111
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
91
112
|
) -> int:
|
|
92
|
-
"""Count conversations using the ACP-capable contract.
|
|
93
|
-
|
|
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)
|
|
94
119
|
|
|
95
120
|
|
|
96
121
|
@conversation_router_acp.get(
|
|
97
122
|
"/{conversation_id}",
|
|
98
123
|
responses={404: {"description": "Item not found"}},
|
|
124
|
+
deprecated=True,
|
|
99
125
|
)
|
|
100
126
|
async def get_acp_conversation(
|
|
101
127
|
conversation_id: UUID,
|
|
128
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
102
129
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
103
130
|
) -> ACPConversationInfo:
|
|
104
|
-
"""Get a conversation using the ACP-capable contract.
|
|
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
|
+
"""
|
|
105
136
|
conversation = await conversation_service.get_acp_conversation(conversation_id)
|
|
106
137
|
if conversation is None:
|
|
107
138
|
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
|
139
|
+
if not include_skills:
|
|
140
|
+
conversation = trim_conversation_response_skills(conversation)
|
|
108
141
|
return conversation
|
|
109
142
|
|
|
110
143
|
|
|
111
|
-
@conversation_router_acp.get("")
|
|
144
|
+
@conversation_router_acp.get("", deprecated=True)
|
|
112
145
|
async def batch_get_acp_conversations(
|
|
113
146
|
ids: Annotated[list[UUID], Query()],
|
|
147
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
114
148
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
115
149
|
) -> list[ACPConversationInfo | None]:
|
|
116
|
-
"""Batch get conversations using the ACP-capable contract.
|
|
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
|
+
"""
|
|
117
155
|
assert len(ids) < 100
|
|
118
|
-
|
|
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
|
|
119
163
|
|
|
120
164
|
|
|
121
|
-
@conversation_router_acp.post("")
|
|
165
|
+
@conversation_router_acp.post("", deprecated=True)
|
|
122
166
|
async def start_acp_conversation(
|
|
123
167
|
request: Annotated[
|
|
124
168
|
StartACPConversationRequest,
|
|
125
169
|
Body(examples=START_ACP_CONVERSATION_EXAMPLES),
|
|
126
170
|
],
|
|
127
171
|
response: Response,
|
|
172
|
+
include_skills: Annotated[bool, Query(title=INCLUDE_SKILLS_PARAM_TITLE)] = False,
|
|
128
173
|
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
129
174
|
) -> ACPConversationInfo:
|
|
130
|
-
"""Start a conversation using the ACP-capable contract.
|
|
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
|
+
"""
|
|
131
181
|
info, is_new = await conversation_service.start_acp_conversation(request)
|
|
132
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)
|
|
133
185
|
return info
|