1010
1111from __future__ import annotations
1212
13+ import asyncio
1314import os
1415from collections .abc import Generator
1516from typing import TYPE_CHECKING , Any
1617
18+ from acp .client .connection import ClientSideConnection
19+ from acp .helpers import text_block
20+ from acp .schema import (
21+ AgentMessageChunk ,
22+ AgentThoughtChunk ,
23+ AllowedOutcome ,
24+ RequestPermissionResponse ,
25+ TextContentBlock ,
26+ ToolCallProgress ,
27+ ToolCallStart ,
28+ UsageUpdate ,
29+ )
30+ from acp .transports import default_environment
1731from pydantic import Field , PrivateAttr
1832
1933from openhands .sdk .agent .base import AgentBase
4155# so we yield to the event loop and then sleep briefly to allow in-flight
4256# handlers to finish. Override via ACP_NOTIFICATION_DRAIN_DELAY for slow or
4357# remote servers.
44- # TODO: Replace with protocol-level synchronization once ACP supports a
45- # "turn complete" sentinel notification.
58+ # TODO(https://github.com/agentclientprotocol/agent-client-protocol/issues/554):
59+ # Replace with protocol-level synchronization once ACP supports a
60+ # "turn complete" notification.
4661_NOTIFICATION_DRAIN_DELAY : float = float (
4762 os .environ .get ("ACP_NOTIFICATION_DRAIN_DELAY" , "0.1" )
4863)
4964
5065
51- def _make_sentinel_llm () -> LLM :
52- """Create a sentinel LLM that should never be called."""
66+ def _make_dummy_llm () -> LLM :
67+ """Create a dummy LLM that should never be called directly ."""
5368 return LLM (model = "acp-managed" )
5469
5570
@@ -78,15 +93,12 @@ async def _filter_jsonrpc_lines(source: Any, dest: Any) -> None:
7893 if stripped .startswith (b"{" ) and b'"jsonrpc"' in line :
7994 dest .feed_data (line )
8095 else :
81- # Log non-JSON lines at debug level
82- try :
83- logger .debug (
84- "ACP stdout (non-JSON): %s" ,
85- line .decode (errors = "replace" ).rstrip (),
86- )
87- except Exception :
88- pass
96+ logger .debug (
97+ "ACP stdout (non-JSON): %s" ,
98+ line .decode (errors = "replace" ).rstrip (),
99+ )
89100 except Exception :
101+ logger .debug ("_filter_jsonrpc_lines stopped" , exc_info = True )
90102 dest .feed_eof ()
91103
92104
@@ -120,15 +132,6 @@ async def session_update(
120132 update : Any ,
121133 ** kwargs : Any , # noqa: ARG002
122134 ) -> None :
123- from acp .schema import (
124- AgentMessageChunk ,
125- AgentThoughtChunk ,
126- TextContentBlock ,
127- ToolCallProgress ,
128- ToolCallStart ,
129- UsageUpdate ,
130- )
131-
132135 if isinstance (update , AgentMessageChunk ):
133136 if isinstance (update .content , TextContentBlock ):
134137 text = update .content .text
@@ -137,7 +140,7 @@ async def session_update(
137140 try :
138141 self .on_token (text )
139142 except Exception :
140- pass
143+ logger . debug ( "on_token callback failed" , exc_info = True )
141144 elif isinstance (update , AgentThoughtChunk ):
142145 if isinstance (update .content , TextContentBlock ):
143146 self .accumulated_thoughts .append (update .content .text )
@@ -163,11 +166,6 @@ async def request_permission(
163166 ** kwargs : Any , # noqa: ARG002
164167 ) -> Any :
165168 """Auto-approve all permission requests from the ACP server."""
166- from acp .schema import (
167- AllowedOutcome ,
168- RequestPermissionResponse ,
169- )
170-
171169 # Pick the first option (usually "allow once")
172170 option_id = options [0 ].option_id if options else "allow_once"
173171 logger .info (
@@ -260,7 +258,7 @@ class ACPAgent(AgentBase):
260258 """
261259
262260 # Override required fields with ACP-appropriate defaults
263- llm : LLM = Field (default_factory = _make_sentinel_llm )
261+ llm : LLM = Field (default_factory = _make_dummy_llm )
264262 tools : list [Tool ] = Field (default_factory = list )
265263 include_default_tools : list [str ] = Field (default_factory = list )
266264
@@ -322,10 +320,6 @@ def init_state(
322320 "ACPAgent does not support condenser; "
323321 "the ACP server manages its own context"
324322 )
325- if self .critic is not None :
326- raise NotImplementedError (
327- "ACPAgent does not support critic; "
328- )
329323 if self .agent_context is not None :
330324 raise NotImplementedError (
331325 "ACPAgent does not support agent_context; "
@@ -354,15 +348,6 @@ def init_state(
354348
355349 def _start_acp_server (self , state : ConversationState ) -> None :
356350 """Start the ACP subprocess and initialize the session."""
357- import asyncio
358-
359- from acp .client .connection import (
360- ClientSideConnection ,
361- )
362- from acp .transports import (
363- default_environment ,
364- )
365-
366351 client = _OpenHandsACPClient ()
367352 client ._llm_ref = self .llm
368353 self ._client = client
@@ -449,9 +434,6 @@ def step(
449434 self ._client .on_token = on_token
450435
451436 try :
452- import asyncio
453-
454- from acp .helpers import text_block
455437
456438 async def _prompt () -> Any :
457439 response = await self ._conn .prompt (
@@ -490,7 +472,7 @@ async def _prompt() -> Any:
490472 try :
491473 self .llm .telemetry ._stats_update_callback ()
492474 except Exception :
493- pass
475+ logger . debug ( "Stats update callback failed" , exc_info = True )
494476
495477 # Build response message
496478 response_text = "" .join (self ._client .accumulated_text )
0 commit comments