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.
Files changed (62) hide show
  1. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/PKG-INFO +1 -1
  2. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/__main__.py +55 -0
  3. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/api.py +4 -0
  4. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/bash_service.py +2 -1
  5. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/config.py +9 -0
  6. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_router.py +55 -16
  7. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_router_acp.py +66 -14
  8. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_service.py +163 -125
  9. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/docker/Dockerfile +99 -45
  10. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/event_service.py +179 -104
  11. openhands_agent_server-1.23.0/openhands/agent_server/mcp_router.py +225 -0
  12. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/models.py +63 -22
  13. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/persistence/__init__.py +16 -0
  14. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/persistence/models.py +89 -19
  15. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/persistence/store.py +108 -2
  16. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/pub_sub.py +28 -4
  17. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/server_details_router.py +10 -0
  18. openhands_agent_server-1.23.0/openhands/agent_server/skills_router.py +530 -0
  19. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/skills_service.py +302 -7
  20. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/sockets.py +36 -7
  21. openhands_agent_server-1.23.0/openhands/agent_server/workspaces_router.py +244 -0
  22. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/PKG-INFO +1 -1
  23. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/SOURCES.txt +4 -0
  24. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/pyproject.toml +1 -1
  25. openhands_agent_server-1.22.0/openhands/agent_server/skills_router.py +0 -192
  26. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/__init__.py +0 -0
  27. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/_secrets_exposure.py +0 -0
  28. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/auth_router.py +0 -0
  29. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/bash_router.py +0 -0
  30. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/cloud_proxy_router.py +0 -0
  31. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/conversation_lease.py +0 -0
  32. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/dependencies.py +0 -0
  33. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/desktop_router.py +0 -0
  34. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/desktop_service.py +0 -0
  35. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/docker/build.py +0 -0
  36. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/docker/wallpaper.svg +0 -0
  37. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/env_parser.py +0 -0
  38. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/event_router.py +0 -0
  39. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/file_router.py +0 -0
  40. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/git_router.py +0 -0
  41. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/hooks_router.py +0 -0
  42. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/hooks_service.py +0 -0
  43. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/llm_router.py +0 -0
  44. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/logging_config.py +0 -0
  45. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/middleware.py +0 -0
  46. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/openapi.py +0 -0
  47. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/profiles_router.py +0 -0
  48. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/py.typed +0 -0
  49. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/settings_router.py +0 -0
  50. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/tool_preload_service.py +0 -0
  51. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/tool_router.py +0 -0
  52. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/utils.py +0 -0
  53. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
  54. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
  55. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_router.py +0 -0
  56. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/vscode_service.py +0 -0
  57. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands/agent_server/workspace_router.py +0 -0
  58. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
  59. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
  60. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/requires.txt +0 -0
  61. {openhands_agent_server-1.22.0 → openhands_agent_server-1.23.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
  62. {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.22.0
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
@@ -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
 
@@ -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](), init=False
35
+ default_factory=lambda: PubSub[BashEventBase](max_subscribers=50),
36
+ init=False,
36
37
  )
37
38
 
38
39
  def _ensure_bash_events_dir(self) -> None:
@@ -174,6 +174,15 @@ class Config(BaseModel):
174
174
  default=True,
175
175
  description="Whether to preload tools",
176
176
  )
177
+ max_concurrent_runs: int = Field(
178
+ default=10,
179
+ ge=1,
180
+ description=(
181
+ "Maximum number of conversations that can execute agent steps "
182
+ "concurrently. Controls the size of the dedicated thread pool "
183
+ "used for conversation.run() calls."
184
+ ),
185
+ )
177
186
  secret_key: SecretStr | None = Field(
178
187
  default_factory=_default_secret_key,
179
188
  description=(
@@ -19,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
- return await conversation_service.search_conversations(
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
- try:
178
- info, is_new = await conversation_service.start_conversation(request)
179
- except ConversationContractMismatchError as e:
180
- raise HTTPException(
181
- status_code=status.HTTP_409_CONFLICT,
182
- detail=str(e),
183
- ) from e
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
- return await conversation_service.search_acp_conversations(
91
+ page = await conversation_service.search_acp_conversations(
80
92
  page_id, limit, status, sort_order
81
93
  )
82
-
83
-
84
- @conversation_router_acp.get("/count")
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
- return await conversation_service.count_acp_conversations(status)
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
- return await conversation_service.batch_get_acp_conversations(ids)
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