agent-client-protocol 0.10.0__tar.gz → 0.11.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 (94) hide show
  1. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/PKG-INFO +1 -1
  2. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/examples/agent.py +3 -4
  3. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/examples/client.py +19 -7
  4. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/examples/echo_agent.py +2 -3
  5. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/examples/gemini.py +33 -7
  6. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/pyproject.toml +1 -1
  7. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/__init__.py +53 -4
  8. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/agent/connection.py +59 -6
  9. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/agent/router.py +0 -9
  10. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/client/connection.py +12 -29
  11. agent_client_protocol-0.11.0/src/acp/client/router.py +181 -0
  12. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/connection.py +21 -3
  13. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/core.py +5 -1
  14. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/interfaces.py +29 -26
  15. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/meta.py +31 -27
  16. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/schema.py +3558 -2744
  17. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/utils.py +1 -1
  18. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/conftest.py +38 -11
  19. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/real_user/test_permission_flow.py +2 -2
  20. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/real_user/test_stdio_limits.py +48 -30
  21. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/test_compatibility.py +0 -5
  22. agent_client_protocol-0.11.0/tests/test_connection_recovery.py +123 -0
  23. agent_client_protocol-0.11.0/tests/test_core.py +55 -0
  24. agent_client_protocol-0.11.0/tests/test_gen_all.py +119 -0
  25. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/test_rpc.py +114 -27
  26. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/test_unstable.py +0 -12
  27. agent_client_protocol-0.10.0/src/acp/client/router.py +0 -95
  28. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/LICENSE +0 -0
  29. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/README.md +0 -0
  30. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/examples/duet.py +0 -0
  31. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/agent/__init__.py +0 -0
  32. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/client/__init__.py +0 -0
  33. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/contrib/__init__.py +0 -0
  34. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/contrib/permissions.py +0 -0
  35. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/contrib/session_state.py +0 -0
  36. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/contrib/tool_calls.py +0 -0
  37. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/exceptions.py +0 -0
  38. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/helpers.py +0 -0
  39. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/py.typed +0 -0
  40. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/router.py +0 -0
  41. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/stdio.py +0 -0
  42. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/task/__init__.py +0 -0
  43. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/task/dispatcher.py +0 -0
  44. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/task/queue.py +0 -0
  45. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/task/sender.py +0 -0
  46. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/task/state.py +0 -0
  47. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/task/supervisor.py +0 -0
  48. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/telemetry.py +0 -0
  49. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/src/acp/transports.py +0 -0
  50. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/contrib/test_contrib_permissions.py +0 -0
  51. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/contrib/test_contrib_session_state.py +0 -0
  52. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/contrib/test_contrib_tool_calls.py +0 -0
  53. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/cancel_notification.json +0 -0
  54. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/content_audio.json +0 -0
  55. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/content_image.json +0 -0
  56. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/content_resource_blob.json +0 -0
  57. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/content_resource_link.json +0 -0
  58. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/content_resource_text.json +0 -0
  59. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/content_text.json +0 -0
  60. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/fs_read_text_file_request.json +0 -0
  61. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/fs_read_text_file_response.json +0 -0
  62. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/fs_write_text_file_request.json +0 -0
  63. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/initialize_request.json +0 -0
  64. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/initialize_response.json +0 -0
  65. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/new_session_request.json +0 -0
  66. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/new_session_response.json +0 -0
  67. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/permission_outcome_cancelled.json +0 -0
  68. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/permission_outcome_selected.json +0 -0
  69. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/prompt_request.json +0 -0
  70. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/request_permission_request.json +0 -0
  71. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/request_permission_response_selected.json +0 -0
  72. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_agent_message_chunk.json +0 -0
  73. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_agent_thought_chunk.json +0 -0
  74. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_config_option_update.json +0 -0
  75. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_plan.json +0 -0
  76. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_tool_call.json +0 -0
  77. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_tool_call_edit.json +0 -0
  78. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_tool_call_locations_rawinput.json +0 -0
  79. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_tool_call_read.json +0 -0
  80. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_tool_call_update_content.json +0 -0
  81. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_tool_call_update_more_fields.json +0 -0
  82. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/session_update_user_message_chunk.json +0 -0
  83. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/set_session_config_option_request.json +0 -0
  84. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/set_session_config_option_response.json +0 -0
  85. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/tool_content_content_text.json +0 -0
  86. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/tool_content_diff.json +0 -0
  87. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/tool_content_diff_no_old.json +0 -0
  88. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/golden/tool_content_terminal.json +0 -0
  89. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/real_user/__init__.py +0 -0
  90. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/real_user/test_cancel_prompt_flow.py +0 -0
  91. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/real_user/test_mcp_servers_optional.py +0 -0
  92. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/test_gemini_example.py +0 -0
  93. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/test_golden.py +0 -0
  94. {agent_client_protocol-0.10.0 → agent_client_protocol-0.11.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agent-client-protocol
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Summary: A Python implement of Agent Client Protocol (ACP, by Zed Industries)
5
5
  Keywords: python
6
6
  Author-Email: Chojan Shang <psiace@apache.org>, Frost Ming <me@frostming.com>
@@ -89,12 +89,13 @@ class ExampleAgent(Agent):
89
89
  self._sessions.add(session_id)
90
90
  return LoadSessionResponse()
91
91
 
92
- async def set_session_mode(self, mode_id: str, session_id: str, **kwargs: Any) -> SetSessionModeResponse | None:
92
+ async def set_session_mode(self, session_id: str, mode_id: str, **kwargs: Any) -> SetSessionModeResponse | None:
93
93
  logging.info("Received set session mode request %s -> %s", session_id, mode_id)
94
94
  return SetSessionModeResponse()
95
95
 
96
96
  async def prompt(
97
97
  self,
98
+ session_id: str,
98
99
  prompt: list[
99
100
  TextContentBlock
100
101
  | ImageContentBlock
@@ -102,8 +103,6 @@ class ExampleAgent(Agent):
102
103
  | ResourceContentBlock
103
104
  | EmbeddedResourceContentBlock
104
105
  ],
105
- session_id: str,
106
- message_id: str | None = None,
107
106
  **kwargs: Any,
108
107
  ) -> PromptResponse:
109
108
  logging.info("Received prompt request for session %s", session_id)
@@ -113,7 +112,7 @@ class ExampleAgent(Agent):
113
112
  await self._send_agent_message(session_id, text_block("Client sent:"))
114
113
  for block in prompt:
115
114
  await self._send_agent_message(session_id, block)
116
- return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
115
+ return PromptResponse(stop_reason="end_turn")
117
116
 
118
117
  async def cancel(self, session_id: str, **kwargs: Any) -> None:
119
118
  logging.info("Received cancel notification for session %s", session_id)
@@ -6,7 +6,6 @@ import os
6
6
  import sys
7
7
  from pathlib import Path
8
8
  from typing import Any
9
- from uuid import uuid4
10
9
 
11
10
  from acp import (
12
11
  PROTOCOL_VERSION,
@@ -18,14 +17,19 @@ from acp import (
18
17
  from acp.core import ClientSideConnection
19
18
  from acp.schema import (
20
19
  AgentMessageChunk,
20
+ AgentPlanContentUpdate,
21
+ AgentPlanRemovedUpdate,
21
22
  AgentPlanUpdate,
22
23
  AgentThoughtChunk,
23
24
  AudioContentBlock,
24
25
  AvailableCommandsUpdate,
25
26
  ClientCapabilities,
26
27
  ConfigOptionUpdate,
28
+ CreateElicitationResponse,
27
29
  CreateTerminalResponse,
28
30
  CurrentModeUpdate,
31
+ DeclineElicitationResponse,
32
+ ElicitationMode,
29
33
  EmbeddedResourceContentBlock,
30
34
  EnvVariable,
31
35
  ImageContentBlock,
@@ -51,27 +55,27 @@ from acp.schema import (
51
55
 
52
56
  class ExampleClient(Client):
53
57
  async def request_permission(
54
- self, options: list[PermissionOption], session_id: str, tool_call: ToolCallUpdate, **kwargs: Any
58
+ self, session_id: str, tool_call: ToolCallUpdate, options: list[PermissionOption], **kwargs: Any
55
59
  ) -> RequestPermissionResponse:
56
60
  raise RequestError.method_not_found("session/request_permission")
57
61
 
58
62
  async def write_text_file(
59
- self, content: str, path: str, session_id: str, **kwargs: Any
63
+ self, session_id: str, path: str, content: str, **kwargs: Any
60
64
  ) -> WriteTextFileResponse | None:
61
65
  raise RequestError.method_not_found("fs/write_text_file")
62
66
 
63
67
  async def read_text_file(
64
- self, path: str, session_id: str, limit: int | None = None, line: int | None = None, **kwargs: Any
68
+ self, session_id: str, path: str, line: int | None = None, limit: int | None = None, **kwargs: Any
65
69
  ) -> ReadTextFileResponse:
66
70
  raise RequestError.method_not_found("fs/read_text_file")
67
71
 
68
72
  async def create_terminal(
69
73
  self,
70
- command: str,
71
74
  session_id: str,
75
+ command: str,
72
76
  args: list[str] | None = None,
73
- cwd: str | None = None,
74
77
  env: list[EnvVariable] | None = None,
78
+ cwd: str | None = None,
75
79
  output_byte_limit: int | None = None,
76
80
  **kwargs: Any,
77
81
  ) -> CreateTerminalResponse:
@@ -93,6 +97,13 @@ class ExampleClient(Client):
93
97
  async def kill_terminal(self, session_id: str, terminal_id: str, **kwargs: Any) -> KillTerminalResponse | None:
94
98
  raise RequestError.method_not_found("terminal/kill")
95
99
 
100
+ async def create_elicitation(self, message: str, mode: ElicitationMode, **kwargs: Any) -> CreateElicitationResponse:
101
+ print(f"| Agent requested input: {message} ({type(mode).__name__})")
102
+ return DeclineElicitationResponse(action="decline")
103
+
104
+ async def complete_elicitation(self, elicitation_id: str, **kwargs: Any) -> None:
105
+ print(f"| Agent completed elicitation: {elicitation_id}")
106
+
96
107
  async def session_update(
97
108
  self,
98
109
  session_id: str,
@@ -102,6 +113,8 @@ class ExampleClient(Client):
102
113
  | ToolCallStart
103
114
  | ToolCallProgress
104
115
  | AgentPlanUpdate
116
+ | AgentPlanContentUpdate
117
+ | AgentPlanRemovedUpdate
105
118
  | AvailableCommandsUpdate
106
119
  | CurrentModeUpdate
107
120
  | ConfigOptionUpdate
@@ -158,7 +171,6 @@ async def interactive_loop(conn: ClientSideConnection, session_id: str) -> None:
158
171
  await conn.prompt(
159
172
  session_id=session_id,
160
173
  prompt=[text_block(line)],
161
- message_id=str(uuid4()),
162
174
  )
163
175
  except Exception as exc:
164
176
  logging.error("Prompt failed: %s", exc) # noqa: TRY400
@@ -58,6 +58,7 @@ class EchoAgent(Agent):
58
58
 
59
59
  async def prompt(
60
60
  self,
61
+ session_id: str,
61
62
  prompt: list[
62
63
  TextContentBlock
63
64
  | ImageContentBlock
@@ -65,8 +66,6 @@ class EchoAgent(Agent):
65
66
  | ResourceContentBlock
66
67
  | EmbeddedResourceContentBlock
67
68
  ],
68
- session_id: str,
69
- message_id: str | None = None,
70
69
  **kwargs: Any,
71
70
  ) -> PromptResponse:
72
71
  for block in prompt:
@@ -76,7 +75,7 @@ class EchoAgent(Agent):
76
75
  chunk.content.field_meta = {"echo": True}
77
76
 
78
77
  await self._conn.session_update(session_id=session_id, update=chunk, source="echo_agent")
79
- return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
78
+ return PromptResponse(stop_reason="end_turn")
80
79
 
81
80
 
82
81
  async def main() -> None:
@@ -22,15 +22,20 @@ from acp import (
22
22
  from acp.core import ClientSideConnection
23
23
  from acp.schema import (
24
24
  AgentMessageChunk,
25
+ AgentPlanContentUpdate,
26
+ AgentPlanRemovedUpdate,
25
27
  AgentPlanUpdate,
26
28
  AgentThoughtChunk,
27
29
  AllowedOutcome,
28
30
  AvailableCommandsUpdate,
29
31
  ClientCapabilities,
30
32
  ConfigOptionUpdate,
33
+ CreateElicitationResponse,
31
34
  CreateTerminalResponse,
32
35
  CurrentModeUpdate,
36
+ DeclineElicitationResponse,
33
37
  DeniedOutcome,
38
+ ElicitationMode,
34
39
  EmbeddedResourceContentBlock,
35
40
  EnvVariable,
36
41
  FileEditToolCallContent,
@@ -62,7 +67,7 @@ class GeminiClient(Client):
62
67
  self._auto_approve = auto_approve
63
68
 
64
69
  async def request_permission(
65
- self, options: list[PermissionOption], session_id: str, tool_call: ToolCallUpdate, **kwargs: Any
70
+ self, session_id: str, tool_call: ToolCallUpdate, options: list[PermissionOption], **kwargs: Any
66
71
  ) -> RequestPermissionResponse:
67
72
  if self._auto_approve:
68
73
  option = _pick_preferred_option(options)
@@ -93,7 +98,7 @@ class GeminiClient(Client):
93
98
  print("Invalid selection, try again.")
94
99
 
95
100
  async def write_text_file(
96
- self, content: str, path: str, session_id: str, **kwargs: Any
101
+ self, session_id: str, path: str, content: str, **kwargs: Any
97
102
  ) -> WriteTextFileResponse | None:
98
103
  pathlib_path = Path(path)
99
104
  if not pathlib_path.is_absolute():
@@ -104,7 +109,7 @@ class GeminiClient(Client):
104
109
  return WriteTextFileResponse()
105
110
 
106
111
  async def read_text_file(
107
- self, path: str, session_id: str, limit: int | None = None, line: int | None = None, **kwargs: Any
112
+ self, session_id: str, path: str, line: int | None = None, limit: int | None = None, **kwargs: Any
108
113
  ) -> ReadTextFileResponse:
109
114
  pathlib_path = Path(path)
110
115
  if not pathlib_path.is_absolute():
@@ -124,6 +129,8 @@ class GeminiClient(Client):
124
129
  | ToolCallStart
125
130
  | ToolCallProgress
126
131
  | AgentPlanUpdate
132
+ | AgentPlanContentUpdate
133
+ | AgentPlanRemovedUpdate
127
134
  | AvailableCommandsUpdate
128
135
  | CurrentModeUpdate
129
136
  | ConfigOptionUpdate
@@ -143,6 +150,10 @@ class GeminiClient(Client):
143
150
  print("\n[plan]")
144
151
  for entry in update.entries:
145
152
  print(f" - {entry.status.upper():<10} {entry.content}")
153
+ elif isinstance(update, AgentPlanContentUpdate):
154
+ print(f"\n[plan update] {update.plan.id}")
155
+ elif isinstance(update, AgentPlanRemovedUpdate):
156
+ print(f"\n[plan removed] {update.id}")
146
157
  elif isinstance(update, ToolCallStart):
147
158
  print(f"\n🔧 {update.title} ({update.status or 'pending'})")
148
159
  elif isinstance(update, ToolCallProgress):
@@ -162,17 +173,24 @@ class GeminiClient(Client):
162
173
  # Optional / terminal-related methods ---------------------------------
163
174
  async def create_terminal(
164
175
  self,
165
- command: str,
166
176
  session_id: str,
177
+ command: str,
167
178
  args: list[str] | None = None,
168
- cwd: str | None = None,
169
179
  env: list[EnvVariable] | None = None,
180
+ cwd: str | None = None,
170
181
  output_byte_limit: int | None = None,
171
182
  **kwargs: Any,
172
183
  ) -> CreateTerminalResponse:
173
184
  print(f"[Client] createTerminal: {command} {args or []} (cwd={cwd})")
174
185
  return CreateTerminalResponse(terminal_id="term-1")
175
186
 
187
+ async def create_elicitation(self, message: str, mode: ElicitationMode, **kwargs: Any) -> CreateElicitationResponse:
188
+ print(f"\n[elicitation] {message} ({type(mode).__name__})")
189
+ return DeclineElicitationResponse(action="decline")
190
+
191
+ async def complete_elicitation(self, elicitation_id: str, **kwargs: Any) -> None:
192
+ print(f"\n[elicitation complete] {elicitation_id}")
193
+
176
194
  async def terminal_output(self, session_id: str, terminal_id: str, **kwargs: Any) -> TerminalOutputResponse:
177
195
  print(f"[Client] terminalOutput: {session_id} {terminal_id}")
178
196
  return TerminalOutputResponse(output="", truncated=False)
@@ -328,7 +346,8 @@ async def run(argv: list[str]) -> int: # noqa: C901
328
346
  *cmd,
329
347
  stdin=asyncio.subprocess.PIPE,
330
348
  stdout=asyncio.subprocess.PIPE,
331
- stderr=None,
349
+ # Suppress Gemini CLI diagnostic noise unless debugging
350
+ stderr=None if args.debug else asyncio.subprocess.DEVNULL,
332
351
  )
333
352
  except FileNotFoundError as exc:
334
353
  print(f"Failed to start Gemini CLI: {exc}", file=sys.stderr)
@@ -412,6 +431,10 @@ def _print_request_error(stage: str, err: RequestError) -> None:
412
431
  async def _shutdown(proc: asyncio.subprocess.Process, conn: ClientSideConnection) -> None:
413
432
  with contextlib.suppress(Exception):
414
433
  await asyncio.wait_for(conn.close(), timeout=2)
434
+ if proc.stdin is not None:
435
+ with contextlib.suppress(Exception):
436
+ proc.stdin.close()
437
+ await asyncio.wait_for(proc.stdin.wait_closed(), timeout=2)
415
438
  if proc.returncode is None:
416
439
  proc.terminate()
417
440
  try:
@@ -424,7 +447,10 @@ async def _shutdown(proc: asyncio.subprocess.Process, conn: ClientSideConnection
424
447
 
425
448
  def main(argv: list[str] | None = None) -> int:
426
449
  args = sys.argv if argv is None else argv
427
- return asyncio.run(run(list(args)))
450
+ try:
451
+ return asyncio.run(run(list(args)))
452
+ except KeyboardInterrupt:
453
+ return 1
428
454
 
429
455
 
430
456
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-client-protocol"
3
- version = "0.10.0"
3
+ version = "0.11.0"
4
4
  description = "A Python implement of Agent Client Protocol (ACP, by Zed Industries)"
5
5
  authors = [
6
6
  { name = "Chojan Shang", email = "psiace@apache.org" },
@@ -38,11 +38,37 @@ from .meta import (
38
38
  PROTOCOL_VERSION,
39
39
  )
40
40
  from .schema import (
41
+ AcceptElicitationResponse,
41
42
  AuthenticateRequest,
42
43
  AuthenticateResponse,
44
+ CancelElicitationResponse,
43
45
  CancelNotification,
46
+ CompleteElicitationNotification,
47
+ CreateElicitationRequest,
48
+ CreateElicitationResponse,
49
+ CreateFormElicitationRequest,
50
+ CreateFormRequestElicitationRequest,
51
+ CreateFormSessionElicitationRequest,
44
52
  CreateTerminalRequest,
45
53
  CreateTerminalResponse,
54
+ CreateUrlElicitationRequest,
55
+ CreateUrlRequestElicitationRequest,
56
+ CreateUrlSessionElicitationRequest,
57
+ DeclineElicitationResponse,
58
+ ElicitationBooleanPropertySchema,
59
+ ElicitationCapabilities,
60
+ ElicitationFormCapabilities,
61
+ ElicitationFormRequestMode,
62
+ ElicitationFormSessionMode,
63
+ ElicitationIntegerPropertySchema,
64
+ ElicitationMode,
65
+ ElicitationMultiSelectPropertySchema,
66
+ ElicitationNumberPropertySchema,
67
+ ElicitationSchema,
68
+ ElicitationStringPropertySchema,
69
+ ElicitationUrlCapabilities,
70
+ ElicitationUrlRequestMode,
71
+ ElicitationUrlSessionMode,
46
72
  InitializeRequest,
47
73
  InitializeResponse,
48
74
  KillTerminalRequest,
@@ -62,8 +88,6 @@ from .schema import (
62
88
  SessionNotification,
63
89
  SetSessionConfigOptionResponse,
64
90
  SetSessionConfigOptionSelectRequest,
65
- SetSessionModelRequest,
66
- SetSessionModelResponse,
67
91
  SetSessionModeRequest,
68
92
  SetSessionModeResponse,
69
93
  TerminalOutputRequest,
@@ -115,10 +139,35 @@ __all__ = [ # noqa: RUF022
115
139
  "SessionNotification",
116
140
  "SetSessionModeRequest",
117
141
  "SetSessionModeResponse",
118
- "SetSessionModelRequest",
119
- "SetSessionModelResponse",
120
142
  "SetSessionConfigOptionSelectRequest",
121
143
  "SetSessionConfigOptionResponse",
144
+ # elicitation types
145
+ "ElicitationMode",
146
+ "ElicitationSchema",
147
+ "ElicitationCapabilities",
148
+ "ElicitationFormCapabilities",
149
+ "ElicitationUrlCapabilities",
150
+ "ElicitationFormSessionMode",
151
+ "ElicitationFormRequestMode",
152
+ "ElicitationUrlSessionMode",
153
+ "ElicitationUrlRequestMode",
154
+ "ElicitationStringPropertySchema",
155
+ "ElicitationNumberPropertySchema",
156
+ "ElicitationIntegerPropertySchema",
157
+ "ElicitationBooleanPropertySchema",
158
+ "ElicitationMultiSelectPropertySchema",
159
+ "CreateElicitationRequest",
160
+ "CreateElicitationResponse",
161
+ "CreateFormElicitationRequest",
162
+ "CreateFormSessionElicitationRequest",
163
+ "CreateFormRequestElicitationRequest",
164
+ "CreateUrlElicitationRequest",
165
+ "CreateUrlSessionElicitationRequest",
166
+ "CreateUrlRequestElicitationRequest",
167
+ "AcceptElicitationResponse",
168
+ "DeclineElicitationResponse",
169
+ "CancelElicitationResponse",
170
+ "CompleteElicitationNotification",
122
171
  # terminal types
123
172
  "CreateTerminalRequest",
124
173
  "CreateTerminalResponse",
@@ -4,18 +4,38 @@ import asyncio
4
4
  from collections.abc import Callable
5
5
  from typing import Any, cast, final
6
6
 
7
+ from pydantic import TypeAdapter
8
+
7
9
  from ..connection import Connection
8
10
  from ..interfaces import Agent, Client
9
11
  from ..meta import CLIENT_METHODS
10
12
  from ..schema import (
13
+ AcceptElicitationResponse,
11
14
  AgentMessageChunk,
15
+ AgentPlanContentUpdate,
16
+ AgentPlanRemovedUpdate,
12
17
  AgentPlanUpdate,
13
18
  AgentThoughtChunk,
14
19
  AvailableCommandsUpdate,
20
+ CancelElicitationResponse,
21
+ CompleteElicitationNotification,
15
22
  ConfigOptionUpdate,
23
+ CreateElicitationResponse,
24
+ CreateFormElicitationRequest,
25
+ CreateFormRequestElicitationRequest,
26
+ CreateFormSessionElicitationRequest,
16
27
  CreateTerminalRequest,
17
28
  CreateTerminalResponse,
29
+ CreateUrlElicitationRequest,
30
+ CreateUrlRequestElicitationRequest,
31
+ CreateUrlSessionElicitationRequest,
18
32
  CurrentModeUpdate,
33
+ DeclineElicitationResponse,
34
+ ElicitationFormRequestMode,
35
+ ElicitationFormSessionMode,
36
+ ElicitationMode,
37
+ ElicitationUrlRequestMode,
38
+ ElicitationUrlSessionMode,
19
39
  EnvVariable,
20
40
  KillTerminalRequest,
21
41
  KillTerminalResponse,
@@ -40,11 +60,12 @@ from ..schema import (
40
60
  WriteTextFileRequest,
41
61
  WriteTextFileResponse,
42
62
  )
43
- from ..utils import compatible_class, notify_model, param_model, request_model, request_optional_model
63
+ from ..utils import compatible_class, notify_model, param_model, request_model, request_optional_model, serialize_params
44
64
  from .router import build_agent_router
45
65
 
46
66
  __all__ = ["AgentSideConnection"]
47
67
  _AGENT_CONNECTION_ERROR = "AgentSideConnection requires asyncio StreamWriter/StreamReader"
68
+ _CREATE_ELICITATION_RESPONSE_ADAPTER = TypeAdapter(CreateElicitationResponse)
48
69
 
49
70
 
50
71
  @final
@@ -86,6 +107,8 @@ class AgentSideConnection:
86
107
  | ToolCallStart
87
108
  | ToolCallProgress
88
109
  | AgentPlanUpdate
110
+ | AgentPlanContentUpdate
111
+ | AgentPlanRemovedUpdate
89
112
  | AvailableCommandsUpdate
90
113
  | CurrentModeUpdate
91
114
  | ConfigOptionUpdate
@@ -101,7 +124,7 @@ class AgentSideConnection:
101
124
 
102
125
  @param_model(RequestPermissionRequest)
103
126
  async def request_permission(
104
- self, options: list[PermissionOption], session_id: str, tool_call: ToolCallUpdate, **kwargs: Any
127
+ self, session_id: str, tool_call: ToolCallUpdate, options: list[PermissionOption], **kwargs: Any
105
128
  ) -> RequestPermissionResponse:
106
129
  return await request_model(
107
130
  self._conn,
@@ -114,7 +137,7 @@ class AgentSideConnection:
114
137
 
115
138
  @param_model(ReadTextFileRequest)
116
139
  async def read_text_file(
117
- self, path: str, session_id: str, limit: int | None = None, line: int | None = None, **kwargs: Any
140
+ self, session_id: str, path: str, line: int | None = None, limit: int | None = None, **kwargs: Any
118
141
  ) -> ReadTextFileResponse:
119
142
  return await request_model(
120
143
  self._conn,
@@ -125,7 +148,7 @@ class AgentSideConnection:
125
148
 
126
149
  @param_model(WriteTextFileRequest)
127
150
  async def write_text_file(
128
- self, content: str, path: str, session_id: str, **kwargs: Any
151
+ self, session_id: str, path: str, content: str, **kwargs: Any
129
152
  ) -> WriteTextFileResponse | None:
130
153
  return await request_optional_model(
131
154
  self._conn,
@@ -137,11 +160,11 @@ class AgentSideConnection:
137
160
  @param_model(CreateTerminalRequest)
138
161
  async def create_terminal(
139
162
  self,
140
- command: str,
141
163
  session_id: str,
164
+ command: str,
142
165
  args: list[str] | None = None,
143
- cwd: str | None = None,
144
166
  env: list[EnvVariable] | None = None,
167
+ cwd: str | None = None,
145
168
  output_byte_limit: int | None = None,
146
169
  **kwargs: Any,
147
170
  ) -> CreateTerminalResponse:
@@ -200,6 +223,21 @@ class AgentSideConnection:
200
223
  KillTerminalResponse,
201
224
  )
202
225
 
226
+ async def create_elicitation(
227
+ self, message: str, mode: ElicitationMode, **kwargs: Any
228
+ ) -> AcceptElicitationResponse | DeclineElicitationResponse | CancelElicitationResponse:
229
+ request = _create_elicitation_request(message, mode, kwargs or None)
230
+ response = await self._conn.send_request(CLIENT_METHODS["elicitation_create"], serialize_params(request))
231
+ return _CREATE_ELICITATION_RESPONSE_ADAPTER.validate_python(response)
232
+
233
+ @param_model(CompleteElicitationNotification)
234
+ async def complete_elicitation(self, elicitation_id: str, **kwargs: Any) -> None:
235
+ await notify_model(
236
+ self._conn,
237
+ CLIENT_METHODS["elicitation_complete"],
238
+ CompleteElicitationNotification(elicitation_id=elicitation_id, field_meta=kwargs or None),
239
+ )
240
+
203
241
  async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
204
242
  return await self._conn.send_request(f"_{method}", params)
205
243
 
@@ -217,3 +255,18 @@ class AgentSideConnection:
217
255
 
218
256
  def on_connect(self, conn: Agent) -> None:
219
257
  pass
258
+
259
+
260
+ def _create_elicitation_request(
261
+ message: str, mode: ElicitationMode, field_meta: dict[str, Any] | None
262
+ ) -> CreateFormElicitationRequest | CreateUrlElicitationRequest:
263
+ mode_fields = mode.model_dump(mode="json", exclude_none=True)
264
+ if isinstance(mode, ElicitationFormSessionMode):
265
+ return CreateFormSessionElicitationRequest(message=message, mode="form", field_meta=field_meta, **mode_fields)
266
+ if isinstance(mode, ElicitationFormRequestMode):
267
+ return CreateFormRequestElicitationRequest(message=message, mode="form", field_meta=field_meta, **mode_fields)
268
+ if isinstance(mode, ElicitationUrlSessionMode):
269
+ return CreateUrlSessionElicitationRequest(message=message, mode="url", field_meta=field_meta, **mode_fields)
270
+ if isinstance(mode, ElicitationUrlRequestMode):
271
+ return CreateUrlRequestElicitationRequest(message=message, mode="url", field_meta=field_meta, **mode_fields)
272
+ raise TypeError(f"Unsupported elicitation mode: {type(mode).__name__}")
@@ -21,7 +21,6 @@ from ..schema import (
21
21
  ResumeSessionRequest,
22
22
  SetSessionConfigOptionBooleanRequest,
23
23
  SetSessionConfigOptionSelectRequest,
24
- SetSessionModelRequest,
25
24
  SetSessionModeRequest,
26
25
  )
27
26
  from ..utils import model_to_kwargs, normalize_result
@@ -83,14 +82,6 @@ def build_agent_router(agent: Agent, use_unstable_protocol: bool = False) -> Mes
83
82
  adapt_result=normalize_result,
84
83
  )
85
84
  router.route_request(AGENT_METHODS["session_prompt"], PromptRequest, agent, "prompt")
86
- router.route_request(
87
- AGENT_METHODS["session_set_model"],
88
- SetSessionModelRequest,
89
- agent,
90
- "set_session_model",
91
- adapt_result=normalize_result,
92
- unstable=True,
93
- )
94
85
  router.add_route(
95
86
  Route(
96
87
  method=AGENT_METHODS["session_set_config_option"],
@@ -8,6 +8,7 @@ from ..connection import Connection
8
8
  from ..interfaces import Agent, Client
9
9
  from ..meta import AGENT_METHODS
10
10
  from ..schema import (
11
+ AcpMcpServer,
11
12
  AudioContentBlock,
12
13
  AuthenticateRequest,
13
14
  AuthenticateResponse,
@@ -38,8 +39,6 @@ from ..schema import (
38
39
  SetSessionConfigOptionBooleanRequest,
39
40
  SetSessionConfigOptionResponse,
40
41
  SetSessionConfigOptionSelectRequest,
41
- SetSessionModelRequest,
42
- SetSessionModelResponse,
43
42
  SetSessionModeRequest,
44
43
  SetSessionModeResponse,
45
44
  SseMcpServer,
@@ -101,7 +100,7 @@ class ClientSideConnection:
101
100
  self,
102
101
  cwd: str,
103
102
  additional_directories: list[str] | None = None,
104
- mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
103
+ mcp_servers: list[HttpMcpServer | SseMcpServer | AcpMcpServer | McpServerStdio] | None = None,
105
104
  **kwargs: Any,
106
105
  ) -> NewSessionResponse:
107
106
  resolved_mcp_servers = mcp_servers or []
@@ -122,8 +121,8 @@ class ClientSideConnection:
122
121
  self,
123
122
  cwd: str,
124
123
  session_id: str,
124
+ mcp_servers: list[HttpMcpServer | SseMcpServer | AcpMcpServer | McpServerStdio] | None = None,
125
125
  additional_directories: list[str] | None = None,
126
- mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
127
126
  **kwargs: Any,
128
127
  ) -> LoadSessionResponse:
129
128
  resolved_mcp_servers = mcp_servers or []
@@ -142,23 +141,17 @@ class ClientSideConnection:
142
141
 
143
142
  @param_model(ListSessionsRequest)
144
143
  async def list_sessions(
145
- self,
146
- additional_directories: list[str] | None = None,
147
- cursor: str | None = None,
148
- cwd: str | None = None,
149
- **kwargs: Any,
144
+ self, cwd: str | None = None, cursor: str | None = None, **kwargs: Any
150
145
  ) -> ListSessionsResponse:
151
146
  return await request_model_from_dict(
152
147
  self._conn,
153
148
  AGENT_METHODS["session_list"],
154
- ListSessionsRequest(
155
- additional_directories=additional_directories, cursor=cursor, cwd=cwd, field_meta=kwargs or None
156
- ),
149
+ ListSessionsRequest(cursor=cursor, cwd=cwd, field_meta=kwargs or None),
157
150
  ListSessionsResponse,
158
151
  )
159
152
 
160
153
  @param_model(SetSessionModeRequest)
161
- async def set_session_mode(self, mode_id: str, session_id: str, **kwargs: Any) -> SetSessionModeResponse:
154
+ async def set_session_mode(self, session_id: str, mode_id: str, **kwargs: Any) -> SetSessionModeResponse:
162
155
  return await request_model_from_dict(
163
156
  self._conn,
164
157
  AGENT_METHODS["session_set_mode"],
@@ -166,15 +159,6 @@ class ClientSideConnection:
166
159
  SetSessionModeResponse,
167
160
  )
168
161
 
169
- @param_model(SetSessionModelRequest)
170
- async def set_session_model(self, model_id: str, session_id: str, **kwargs: Any) -> SetSessionModelResponse:
171
- return await request_model_from_dict(
172
- self._conn,
173
- AGENT_METHODS["session_set_model"],
174
- SetSessionModelRequest(model_id=model_id, session_id=session_id, field_meta=kwargs or None),
175
- SetSessionModelResponse,
176
- )
177
-
178
162
  @param_models(SetSessionConfigOptionBooleanRequest, SetSessionConfigOptionSelectRequest)
179
163
  async def set_config_option(
180
164
  self, config_id: str, session_id: str, value: str | bool, **kwargs: Any
@@ -204,6 +188,7 @@ class ClientSideConnection:
204
188
  @param_model(PromptRequest)
205
189
  async def prompt(
206
190
  self,
191
+ session_id: str,
207
192
  prompt: list[
208
193
  TextContentBlock
209
194
  | ImageContentBlock
@@ -211,24 +196,22 @@ class ClientSideConnection:
211
196
  | ResourceContentBlock
212
197
  | EmbeddedResourceContentBlock
213
198
  ],
214
- session_id: str,
215
- message_id: str | None = None,
216
199
  **kwargs: Any,
217
200
  ) -> PromptResponse:
218
201
  return await request_model(
219
202
  self._conn,
220
203
  AGENT_METHODS["session_prompt"],
221
- PromptRequest(prompt=prompt, session_id=session_id, message_id=message_id, field_meta=kwargs or None),
204
+ PromptRequest(prompt=prompt, session_id=session_id, field_meta=kwargs or None),
222
205
  PromptResponse,
223
206
  )
224
207
 
225
208
  @param_model(ForkSessionRequest)
226
209
  async def fork_session(
227
210
  self,
228
- cwd: str,
229
211
  session_id: str,
212
+ cwd: str,
230
213
  additional_directories: list[str] | None = None,
231
- mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
214
+ mcp_servers: list[HttpMcpServer | SseMcpServer | AcpMcpServer | McpServerStdio] | None = None,
232
215
  **kwargs: Any,
233
216
  ) -> ForkSessionResponse:
234
217
  return await request_model(
@@ -247,10 +230,10 @@ class ClientSideConnection:
247
230
  @param_model(ResumeSessionRequest)
248
231
  async def resume_session(
249
232
  self,
250
- cwd: str,
251
233
  session_id: str,
234
+ cwd: str,
252
235
  additional_directories: list[str] | None = None,
253
- mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
236
+ mcp_servers: list[HttpMcpServer | SseMcpServer | AcpMcpServer | McpServerStdio] | None = None,
254
237
  **kwargs: Any,
255
238
  ) -> ResumeSessionResponse:
256
239
  return await request_model(