openhands-agent-server 1.44.0__tar.gz → 1.44.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.
Files changed (87) hide show
  1. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/PKG-INFO +1 -1
  2. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/agent_profiles_router.py +7 -3
  3. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/config.py +15 -0
  4. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/conversation_service.py +65 -5
  5. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/docker/Dockerfile +35 -7
  6. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/docker/build.py +36 -0
  7. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/pub_sub.py +7 -1
  8. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/sockets.py +3 -0
  9. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/PKG-INFO +1 -1
  10. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/pyproject.toml +1 -1
  11. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/__init__.py +0 -0
  12. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/__main__.py +0 -0
  13. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/_secret_redaction.py +0 -0
  14. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/_secrets_exposure.py +0 -0
  15. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/api.py +0 -0
  16. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/auth_router.py +0 -0
  17. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/bash_router.py +0 -0
  18. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/bash_service.py +0 -0
  19. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/canvas_extensions/__init__.py +0 -0
  20. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/canvas_extensions/installed.py +0 -0
  21. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/canvas_extensions/manifest.py +0 -0
  22. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/canvas_extensions_router.py +0 -0
  23. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/conversation_lease.py +0 -0
  24. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/conversation_router.py +0 -0
  25. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/credential_binding.py +0 -0
  26. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/dependencies.py +0 -0
  27. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/desktop_router.py +0 -0
  28. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/desktop_service.py +0 -0
  29. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/docker/wallpaper.svg +0 -0
  30. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/env_parser.py +0 -0
  31. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/event_router.py +0 -0
  32. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/event_service.py +0 -0
  33. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/file_router.py +0 -0
  34. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/git_router.py +0 -0
  35. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/hooks_router.py +0 -0
  36. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/hooks_service.py +0 -0
  37. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/init_router.py +0 -0
  38. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/llm_router.py +0 -0
  39. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/logging_config.py +0 -0
  40. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/mcp_oauth_store.py +0 -0
  41. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/mcp_router.py +0 -0
  42. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/middleware.py +0 -0
  43. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/models.py +0 -0
  44. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/__init__.py +0 -0
  45. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/models.py +0 -0
  46. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/router.py +0 -0
  47. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/service.py +0 -0
  48. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/openapi.py +0 -0
  49. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/persistence/__init__.py +0 -0
  50. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/persistence/models.py +0 -0
  51. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/persistence/store.py +0 -0
  52. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/plugins_router.py +0 -0
  53. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/plugins_service.py +0 -0
  54. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/profiles_router.py +0 -0
  55. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/provider_connections_router.py +0 -0
  56. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/py.typed +0 -0
  57. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/server_details_router.py +0 -0
  58. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/settings_router.py +0 -0
  59. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/skills_router.py +0 -0
  60. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/skills_service.py +0 -0
  61. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/sub_agents_router.py +0 -0
  62. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/__init__.py +0 -0
  63. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/factory.py +0 -0
  64. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/http_exporter.py +0 -0
  65. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/models.py +0 -0
  66. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/policy.py +0 -0
  67. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/posthog_exporter.py +0 -0
  68. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/sanitizer.py +0 -0
  69. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/service.py +0 -0
  70. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/sink.py +0 -0
  71. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/subscriber.py +0 -0
  72. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry_types.py +0 -0
  73. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/tool_preload_service.py +0 -0
  74. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/tool_router.py +0 -0
  75. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/utils.py +0 -0
  76. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
  77. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
  78. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_router.py +0 -0
  79. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_service.py +0 -0
  80. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/workspace_router.py +0 -0
  81. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands/agent_server/workspaces_router.py +0 -0
  82. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/SOURCES.txt +0 -0
  83. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
  84. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/entry_points.txt +0 -0
  85. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/requires.txt +0 -0
  86. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/top_level.txt +0 -0
  87. {openhands_agent_server-1.44.0 → openhands_agent_server-1.44.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-agent-server
3
- Version: 1.44.0
3
+ Version: 1.44.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
@@ -514,13 +514,17 @@ async def materialize_agent_profile(
514
514
  mcp_config = settings.agent_settings.mcp_config
515
515
 
516
516
  # Discover skills off the event loop so the dry-run can report which skills
517
- # (catalog minus ``disabled_skills``) resolve. Only OpenHands profiles carry
518
- # user/public skills; ACP profiles do not. A discovery failure must not 500
517
+ # (catalog minus ``disabled_skills``) resolve. Mirrors the launch rule in
518
+ # ``conversation_service._resolve_agent_from_profile`` so the preview matches
519
+ # a real launch: an ACP profile is only given a catalog where the CLI cannot
520
+ # read the user's own configuration (#4019). A discovery failure must not 500
519
521
  # the preview: pass ``available_skills=None`` and surface the failure as its
520
522
  # own diagnostic below.
521
523
  discovery_error: str | None = None
522
524
  available_skills = None
523
- if profile.agent_kind == "openhands":
525
+ if profile.agent_kind == "openhands" or (
526
+ config.acp_skill_sourcing == "openhands_managed"
527
+ ):
524
528
  try:
525
529
  available_skills = await asyncio.to_thread(discover_profile_skills)
526
530
  except Exception as exc:
@@ -27,6 +27,7 @@ CONFIG_PATH_ENV = "OPENHANDS_AGENT_SERVER_CONFIG_PATH"
27
27
  DEFAULT_CONFIG_PATH = Path("workspace/openhands_agent_server_config.json")
28
28
  # 20 minutes, matching the idle timeout used by OpenHands Cloud.
29
29
  DEFAULT_CONVERSATION_IDLE_TTL_SECONDS: Final[float] = 20 * 60.0
30
+ ACPSkillSourcing = Literal["native", "openhands_managed"]
30
31
  _logger = logging.getLogger(__name__)
31
32
 
32
33
 
@@ -349,6 +350,20 @@ class Config(BaseModel):
349
350
  "The URL where this agent server instance is available externally"
350
351
  ),
351
352
  )
353
+ acp_skill_sourcing: ACPSkillSourcing = Field(
354
+ default="native",
355
+ description=(
356
+ "Who supplies an ACP agent's skills. 'native' (the default, for a "
357
+ "host-local agent-server): nobody but the ACP CLI — it reads the "
358
+ "user's own home configuration and the repository, so OpenHands "
359
+ "injects none of its managed skills. 'openhands_managed' (for "
360
+ "container runtimes, where that host configuration is absent): also "
361
+ "inject the user/org/public/marketplace skills the server "
362
+ "discovers. Project/repository skills are never injected either way "
363
+ "— the CLI reads AGENTS.md itself (#4019). Set explicitly per "
364
+ "deployment; the agent-server image sets 'openhands_managed'."
365
+ ),
366
+ )
352
367
  registered_marketplaces: list[MarketplaceRegistration] = Field(
353
368
  default_factory=list,
354
369
  description=(
@@ -15,7 +15,7 @@ from weakref import WeakValueDictionary
15
15
  import httpx
16
16
  from pydantic import BaseModel
17
17
 
18
- from openhands.agent_server.config import Config, WebhookSpec
18
+ from openhands.agent_server.config import ACPSkillSourcing, Config, WebhookSpec
19
19
  from openhands.agent_server.conversation_lease import (
20
20
  DEFAULT_LEASE_TTL_SECONDS,
21
21
  ConversationLeaseHeldError,
@@ -290,11 +290,53 @@ def _same_workspace(a: LocalWorkspace, b: LocalWorkspace) -> bool:
290
290
  return Path(a.working_dir).resolve() == Path(b.working_dir).resolve()
291
291
 
292
292
 
293
+ def _apply_acp_skill_sourcing(
294
+ agent: "AgentBase", sourcing: ACPSkillSourcing
295
+ ) -> "AgentBase":
296
+ """Strip OpenHands-managed skills from an ACP agent under ``native`` sourcing.
297
+
298
+ A host-local ACP CLI reads the user's own skills from its home directory, so
299
+ a second, OpenHands-managed set injected into its prompt is at best noise —
300
+ and the catalog listing tells it to call ``invoke_skill``, a tool no ACP
301
+ agent has. Container runtimes set ``openhands_managed`` because that home
302
+ configuration is absent there. Project skills are excluded either way, by
303
+ ``ACPAgent`` itself (#4019).
304
+
305
+ A caller that sends ``agent`` / ``agent_settings`` puts its own skills on the
306
+ context, so the strip happens here rather than at profile resolution.
307
+ """
308
+ if sourcing != "native" or not isinstance(agent, ACPAgent):
309
+ return agent
310
+ context = agent.agent_context
311
+ if context is None:
312
+ return agent
313
+ if not (
314
+ context.skills
315
+ or context.load_user_skills
316
+ or context.load_public_skills
317
+ or context.registered_marketplaces
318
+ ):
319
+ return agent
320
+ return agent.model_copy(
321
+ update={
322
+ "agent_context": context.model_copy(
323
+ update={
324
+ "skills": [],
325
+ "load_user_skills": False,
326
+ "load_public_skills": False,
327
+ "registered_marketplaces": [],
328
+ }
329
+ )
330
+ }
331
+ )
332
+
333
+
293
334
  def _resolve_agent_from_profile(
294
335
  profile_id: "UUID",
295
336
  cipher: "Cipher | None",
296
337
  mcp_config: "dict[str, MCPServer]",
297
338
  load_memory: bool = False,
339
+ acp_skill_sourcing: ACPSkillSourcing = "native",
298
340
  ) -> "tuple[AgentBase, LaunchedAgentProfile]":
299
341
  """Load and resolve an agent profile by id, returning the built agent + provenance.
300
342
 
@@ -310,6 +352,9 @@ def _resolve_agent_from_profile(
310
352
  has no ``agent_context`` field, so the preference cannot ride the
311
353
  profile — it is stamped onto the resolved agent below, else a
312
354
  profile-launched conversation would silently ignore the setting.
355
+ acp_skill_sourcing: This deployment's ACP skill policy
356
+ (``Config.acp_skill_sourcing``). Decides whether an ACP profile is
357
+ resolved with the server's managed skill catalog or with none.
313
358
 
314
359
  Raises:
315
360
  ProfileNotFound: No stored profile has ``profile_id``.
@@ -340,11 +385,15 @@ def _resolve_agent_from_profile(
340
385
  ) from exc
341
386
 
342
387
  # OpenHands profiles get the discovered catalog minus their ``disabled_skills``
343
- # deny-list; ACP profiles carry no user/public skills so discovery is skipped.
344
- # A genuine discovery failure fails the launch loudly rather than silently
345
- # producing a zero-skill agent.
388
+ # deny-list. An ACP profile gets it only where the CLI cannot reach the user's
389
+ # own configuration (``openhands_managed``); under ``native`` it sources its
390
+ # own skills and OpenHands injects none (#4019). A genuine discovery failure
391
+ # fails the launch loudly rather than silently producing a zero-skill agent.
346
392
  available_skills = None
347
- if profile.agent_kind == "openhands":
393
+ wants_skills = profile.agent_kind == "openhands" or (
394
+ acp_skill_sourcing == "openhands_managed"
395
+ )
396
+ if wants_skills:
348
397
  try:
349
398
  available_skills = discover_profile_skills()
350
399
  except Exception as exc:
@@ -641,6 +690,7 @@ class ConversationService:
641
690
  conversation_worktree_root: Path = field(
642
691
  default=Path("/tmp/conversation-worktrees")
643
692
  )
693
+ acp_skill_sourcing: ACPSkillSourcing = "native"
644
694
  _event_services: dict[UUID, EventService] | None = field(default=None, init=False)
645
695
  _conversation_records: dict[UUID, _ConversationRecord] = field(
646
696
  default_factory=dict, init=False
@@ -1533,9 +1583,18 @@ class ConversationService:
1533
1583
  self.cipher,
1534
1584
  mcp_config,
1535
1585
  load_memory=bool(stored_context and stored_context.load_memory),
1586
+ acp_skill_sourcing=self.acp_skill_sourcing,
1536
1587
  )
1537
1588
  request = request.model_copy(update={"agent": resolved_agent})
1538
1589
 
1590
+ request = request.model_copy(
1591
+ update={
1592
+ "agent": _apply_acp_skill_sourcing(
1593
+ request.agent, self.acp_skill_sourcing
1594
+ )
1595
+ }
1596
+ )
1597
+
1539
1598
  additions = request.agent_launch_additions
1540
1599
  suffix = (
1541
1600
  additions.system_message_suffix_append.strip()
@@ -2238,6 +2297,7 @@ class ConversationService:
2238
2297
  lease_ttl_seconds=config.lease_ttl_seconds,
2239
2298
  conversation_idle_ttl_seconds=config.conversation_idle_ttl_seconds,
2240
2299
  conversation_worktree_root=config.conversation_worktree_root,
2300
+ acp_skill_sourcing=config.acp_skill_sourcing,
2241
2301
  )
2242
2302
 
2243
2303
  async def _start_event_service(
@@ -13,6 +13,12 @@ ARG PORT=8000
13
13
  # to bundle google-cloud-aiplatform so the resulting binary supports
14
14
  # `vertex_ai/*` partner models (MiniMax, Qwen, Kimi MaaS endpoints).
15
15
  ARG ENABLE_VERTEX=0
16
+ # Comma-separated ACP provider keys (see ACP_PROVIDERS in
17
+ # openhands-sdk/openhands/sdk/settings/acp_providers.py) to bake into the
18
+ # acp-providers stage below. The default reproduces the full provider set
19
+ # shipped today; an unknown key fails the build rather than silently
20
+ # installing nothing.
21
+ ARG INSTALL_ACP_PROVIDERS=claude-code,codex,gemini-cli
16
22
 
17
23
  ####################################################################################
18
24
  # Builder (source mode)
@@ -92,6 +98,7 @@ RUN test -x /agent-server/dist/openhands-agent-server
92
98
  # the per-target-image fallback).
93
99
  ####################################################################################
94
100
  FROM python:3.13-bookworm AS acp-providers
101
+ ARG INSTALL_ACP_PROVIDERS
95
102
  # A fresh top-level path, not nested under a directory (e.g. /opt) that base
96
103
  # images commonly already populate (SWE-bench images ship /opt/miniconda3):
97
104
  # COPY --from=<stage> only produces a byte-identical layer across different
@@ -100,12 +107,30 @@ FROM python:3.13-bookworm AS acp-providers
100
107
  # stage above.
101
108
  ENV ACP_NODE_DIR=/acp-node
102
109
  RUN set -eux; \
110
+ ACP_WRAPPER_DIR=/opt/acp-wrappers; \
111
+ mkdir -p "$ACP_NODE_DIR" "$ACP_WRAPPER_DIR"; \
112
+ PACKAGES=""; \
113
+ WRAPPER_BINS=""; \
114
+ for provider in $(echo "$INSTALL_ACP_PROVIDERS" | tr ',' ' '); do \
115
+ case "$provider" in \
116
+ claude-code) PACKAGES="$PACKAGES @agentclientprotocol/claude-agent-acp@0.63.0"; \
117
+ WRAPPER_BINS="$WRAPPER_BINS claude-agent-acp";; \
118
+ codex) PACKAGES="$PACKAGES @agentclientprotocol/codex-acp@1.1.7"; \
119
+ WRAPPER_BINS="$WRAPPER_BINS codex-acp";; \
120
+ gemini-cli) PACKAGES="$PACKAGES @google/gemini-cli@0.46.0"; \
121
+ WRAPPER_BINS="$WRAPPER_BINS gemini";; \
122
+ *) echo "Unknown ACP provider '$provider' in INSTALL_ACP_PROVIDERS" \
123
+ "(expected one of: claude-code, codex, gemini-cli)" >&2; exit 1;; \
124
+ esac; \
125
+ done; \
126
+ if [ -z "$PACKAGES" ]; then \
127
+ echo "INSTALL_ACP_PROVIDERS is empty; no ACP providers will be installed"; \
128
+ exit 0; \
129
+ fi; \
103
130
  apt-get -o Acquire::Retries=5 update; \
104
131
  apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
105
132
  ca-certificates curl xz-utils; \
106
133
  rm -rf /var/lib/apt/lists/*; \
107
- ACP_WRAPPER_DIR=/opt/acp-wrappers; \
108
- mkdir -p "$ACP_NODE_DIR" "$ACP_WRAPPER_DIR"; \
109
134
  ARCH=$(uname -m); \
110
135
  case "$ARCH" in \
111
136
  x86_64|amd64) NARCH=x64; NODE_SHA256=69b09dba5c8dcb05c4e4273a4340db1005abeafe3927efda2bc5b249e80437ec;; \
@@ -119,11 +144,8 @@ RUN set -eux; \
119
144
  tar -xJ --strip-components=1 -C "$ACP_NODE_DIR" -f "$NODE_TARBALL"; \
120
145
  rm -f "$NODE_TARBALL"; \
121
146
  "$ACP_NODE_DIR/bin/node" --version; \
122
- PATH="$ACP_NODE_DIR/bin:$PATH" "$ACP_NODE_DIR/bin/npm" install -g \
123
- @agentclientprotocol/claude-agent-acp@0.63.0 \
124
- @agentclientprotocol/codex-acp@1.1.7 \
125
- @google/gemini-cli@0.46.0; \
126
- for bin in claude-agent-acp codex-acp gemini; do \
147
+ PATH="$ACP_NODE_DIR/bin:$PATH" "$ACP_NODE_DIR/bin/npm" install -g $PACKAGES; \
148
+ for bin in $WRAPPER_BINS; do \
127
149
  printf '#!/bin/sh\nPATH="%s/bin:$PATH" exec "%s/bin/%s" "$@"\n' \
128
150
  "$ACP_NODE_DIR" "$ACP_NODE_DIR" "$bin" \
129
151
  > "$ACP_WRAPPER_DIR/$bin"; \
@@ -244,6 +266,9 @@ ENV LC_ALL=C.UTF-8
244
266
  ENV LANG=C.UTF-8
245
267
  ENV OH_ENABLE_VNC=false
246
268
  ENV LOG_JSON=true
269
+ # The ACP CLIs run without the user's host home here, so OpenHands supplies the
270
+ # managed skill catalog they cannot read for themselves (#4019).
271
+ ENV OH_ACP_SKILL_SOURCING=openhands_managed
247
272
  EXPOSE ${PORT}
248
273
 
249
274
  ####################################################################################
@@ -356,6 +381,9 @@ USER ${USERNAME}
356
381
  WORKDIR /
357
382
  ENV OH_ENABLE_VNC=false
358
383
  ENV LOG_JSON=true
384
+ # The ACP CLIs run without the user's host home here, so OpenHands supplies the
385
+ # managed skill catalog they cannot read for themselves (#4019).
386
+ ENV OH_ACP_SKILL_SOURCING=openhands_managed
359
387
  EXPOSE ${PORT} ${NOVNC_PORT}
360
388
 
361
389
 
@@ -31,6 +31,7 @@ from pathlib import Path
31
31
  from pydantic import BaseModel, Field, field_validator
32
32
 
33
33
  from openhands.sdk.logger import IN_CI, get_logger, rolling_log_view
34
+ from openhands.sdk.settings.acp_providers import ACP_PROVIDERS
34
35
  from openhands.sdk.workspace import PlatformType, TargetType
35
36
 
36
37
 
@@ -433,6 +434,27 @@ class BuildOptions(BaseModel):
433
434
  "(e.g., at each release)."
434
435
  ),
435
436
  )
437
+ install_acp_providers: str = Field(
438
+ default="claude-code,codex,gemini-cli",
439
+ description=(
440
+ "Comma-separated ACP provider keys (see ACP_PROVIDERS in "
441
+ "openhands-sdk/openhands/sdk/settings/acp_providers.py) to bake "
442
+ "into the image. Empty string installs none."
443
+ ),
444
+ )
445
+
446
+ @field_validator("install_acp_providers")
447
+ @classmethod
448
+ def _valid_install_acp_providers(cls, v: str) -> str:
449
+ unknown = [
450
+ p for p in (t.strip() for t in v.split(",")) if p and p not in ACP_PROVIDERS
451
+ ]
452
+ if unknown:
453
+ raise ValueError(
454
+ f"Unknown ACP provider(s) in install_acp_providers: "
455
+ f"{', '.join(unknown)}. Valid keys: {', '.join(ACP_PROVIDERS)}"
456
+ )
457
+ return v
436
458
 
437
459
  @property
438
460
  def short_sha(self) -> str:
@@ -834,6 +856,8 @@ def build_with_telemetry(opts: BuildOptions) -> BuildResult:
834
856
  f"OPENHANDS_BUILD_GIT_SHA={opts.git_sha}",
835
857
  "--build-arg",
836
858
  f"OPENHANDS_BUILD_GIT_REF={opts.git_ref}",
859
+ "--build-arg",
860
+ f"INSTALL_ACP_PROVIDERS={opts.install_acp_providers}",
837
861
  ]
838
862
  if push:
839
863
  args += ["--platform", ",".join(opts.platforms), "--push"]
@@ -1051,6 +1075,16 @@ def main(argv: list[str]) -> int:
1051
1075
  "as v1 and v1.2) in output. Should only be used for release builds."
1052
1076
  ),
1053
1077
  )
1078
+ parser.add_argument(
1079
+ "--install-acp-providers",
1080
+ # os.environ.get, not _env(): an explicit empty string here means
1081
+ # "install none" and must survive, but _env() treats blank as unset.
1082
+ default=os.environ.get("INSTALL_ACP_PROVIDERS", "claude-code,codex,gemini-cli"),
1083
+ help=(
1084
+ "Comma-separated ACP provider keys to bake into the image "
1085
+ "(default from $INSTALL_ACP_PROVIDERS; empty string installs none)."
1086
+ ),
1087
+ )
1054
1088
 
1055
1089
  args = parser.parse_args(argv)
1056
1090
 
@@ -1080,6 +1114,7 @@ def main(argv: list[str]) -> int:
1080
1114
  prebuilt_sdist=args.prebuilt_sdist,
1081
1115
  arch=args.arch or None,
1082
1116
  include_versioned_tag=args.versioned_tag,
1117
+ install_acp_providers=args.install_acp_providers,
1083
1118
  )
1084
1119
 
1085
1120
  # If running in GitHub Actions, write outputs directly to GITHUB_OUTPUT
@@ -1128,6 +1163,7 @@ def main(argv: list[str]) -> int:
1128
1163
  prebuilt_sdist=args.prebuilt_sdist,
1129
1164
  arch=args.arch or None,
1130
1165
  include_versioned_tag=args.versioned_tag,
1166
+ install_acp_providers=args.install_acp_providers,
1131
1167
  )
1132
1168
  tags = build(opts)
1133
1169
 
@@ -1,9 +1,10 @@
1
1
  import asyncio
2
2
  from abc import ABC, abstractmethod
3
3
  from dataclasses import dataclass, field
4
- from typing import TypeVar
4
+ from typing import ClassVar, TypeVar
5
5
  from uuid import UUID, uuid4
6
6
 
7
+ from openhands.sdk.event import StreamingDeltaEvent
7
8
  from openhands.sdk.logger import get_logger
8
9
 
9
10
 
@@ -13,6 +14,9 @@ T = TypeVar("T")
13
14
 
14
15
 
15
16
  class Subscriber[T](ABC):
17
+ # Deltas arrive at token rate; consumers opt in rather than inherit them.
18
+ receives_streaming_deltas: ClassVar[bool] = False
19
+
16
20
  @abstractmethod
17
21
  async def __call__(self, event: T):
18
22
  """Invoke this subscriber"""
@@ -87,6 +91,8 @@ class PubSub[T]:
87
91
  event: The event to pass to all callbacks
88
92
  """
89
93
  subscribers = list(self._subscribers.items())
94
+ if isinstance(event, StreamingDeltaEvent):
95
+ subscribers = [s for s in subscribers if s[1].receives_streaming_deltas]
90
96
  if not subscribers:
91
97
  return
92
98
 
@@ -539,6 +539,9 @@ def _is_websocket_connected(websocket: WebSocket) -> bool:
539
539
  class _WebSocketSubscriber(Subscriber):
540
540
  """WebSocket subscriber for conversation events."""
541
541
 
542
+ # The live socket is what token streaming is for.
543
+ receives_streaming_deltas = True
544
+
542
545
  websocket: WebSocket
543
546
 
544
547
  async def __call__(self, event: Event):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-agent-server
3
- Version: 1.44.0
3
+ Version: 1.44.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhands-agent-server"
3
- version = "1.44.0"
3
+ version = "1.44.1"
4
4
  description = "OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent"
5
5
 
6
6
  requires-python = ">=3.12"