Skip to content

Commit 6614d69

Browse files
committed
Added support for http transport via websockets
1 parent 2157358 commit 6614d69

8 files changed

Lines changed: 1162 additions & 0 deletions

File tree

examples/http_client.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""HTTP/WebSocket client example for ACP.
2+
3+
This example shows how to connect to an ACP agent over HTTP/WebSocket using
4+
the connect_http_agent function. This is the client-side counterpart to
5+
http_echo_agent.py.
6+
7+
Requirements:
8+
pip install agent-client-protocol
9+
10+
Usage:
11+
# Start the server first (in another terminal):
12+
python examples/http_echo_agent.py
13+
14+
# Then run the client:
15+
python examples/http_client.py ws://localhost:8080/ws
16+
"""
17+
18+
import asyncio
19+
import logging
20+
import sys
21+
from typing import Any
22+
23+
from acp import (
24+
PROTOCOL_VERSION,
25+
Client,
26+
RequestError,
27+
text_block,
28+
)
29+
from acp.http import connect_http_agent
30+
from acp.schema import (
31+
AgentMessageChunk,
32+
AgentPlanUpdate,
33+
AgentThoughtChunk,
34+
AudioContentBlock,
35+
AvailableCommandsUpdate,
36+
ClientCapabilities,
37+
CreateTerminalResponse,
38+
CurrentModeUpdate,
39+
EmbeddedResourceContentBlock,
40+
EnvVariable,
41+
ImageContentBlock,
42+
Implementation,
43+
KillTerminalCommandResponse,
44+
PermissionOption,
45+
ReadTextFileResponse,
46+
ReleaseTerminalResponse,
47+
RequestPermissionResponse,
48+
ResourceContentBlock,
49+
TerminalOutputResponse,
50+
TextContentBlock,
51+
ToolCall,
52+
ToolCallProgress,
53+
ToolCallStart,
54+
UserMessageChunk,
55+
WaitForTerminalExitResponse,
56+
WriteTextFileResponse,
57+
)
58+
59+
logging.basicConfig(level=logging.INFO)
60+
logger = logging.getLogger(__name__)
61+
62+
63+
class ExampleClient(Client):
64+
"""Example client implementation with minimal method stubs."""
65+
66+
async def request_permission(
67+
self, options: list[PermissionOption], session_id: str, tool_call: ToolCall, **kwargs: Any
68+
) -> RequestPermissionResponse:
69+
raise RequestError.method_not_found("session/request_permission")
70+
71+
async def write_text_file(
72+
self, content: str, path: str, session_id: str, **kwargs: Any
73+
) -> WriteTextFileResponse | None:
74+
raise RequestError.method_not_found("fs/write_text_file")
75+
76+
async def read_text_file(
77+
self, path: str, session_id: str, limit: int | None = None, line: int | None = None, **kwargs: Any
78+
) -> ReadTextFileResponse:
79+
raise RequestError.method_not_found("fs/read_text_file")
80+
81+
async def create_terminal(
82+
self,
83+
command: str,
84+
session_id: str,
85+
args: list[str] | None = None,
86+
cwd: str | None = None,
87+
env: list[EnvVariable] | None = None,
88+
output_byte_limit: int | None = None,
89+
**kwargs: Any,
90+
) -> CreateTerminalResponse:
91+
raise RequestError.method_not_found("terminal/create")
92+
93+
async def terminal_output(self, session_id: str, terminal_id: str, **kwargs: Any) -> TerminalOutputResponse:
94+
raise RequestError.method_not_found("terminal/output")
95+
96+
async def release_terminal(
97+
self, session_id: str, terminal_id: str, **kwargs: Any
98+
) -> ReleaseTerminalResponse | None:
99+
raise RequestError.method_not_found("terminal/release")
100+
101+
async def wait_for_terminal_exit(
102+
self, session_id: str, terminal_id: str, **kwargs: Any
103+
) -> WaitForTerminalExitResponse:
104+
raise RequestError.method_not_found("terminal/wait_for_exit")
105+
106+
async def kill_terminal(
107+
self, session_id: str, terminal_id: str, **kwargs: Any
108+
) -> KillTerminalCommandResponse | None:
109+
raise RequestError.method_not_found("terminal/kill")
110+
111+
async def session_update(
112+
self,
113+
session_id: str,
114+
update: UserMessageChunk
115+
| AgentMessageChunk
116+
| AgentThoughtChunk
117+
| ToolCallStart
118+
| ToolCallProgress
119+
| AgentPlanUpdate
120+
| AvailableCommandsUpdate
121+
| CurrentModeUpdate,
122+
**kwargs: Any,
123+
) -> None:
124+
"""Handle session updates from the agent."""
125+
if not isinstance(update, AgentMessageChunk):
126+
return
127+
128+
content = update.content
129+
text: str
130+
if isinstance(content, TextContentBlock):
131+
text = content.text
132+
elif isinstance(content, ImageContentBlock):
133+
text = "<image>"
134+
elif isinstance(content, AudioContentBlock):
135+
text = "<audio>"
136+
elif isinstance(content, ResourceContentBlock):
137+
text = content.uri or "<resource>"
138+
elif isinstance(content, EmbeddedResourceContentBlock):
139+
text = "<resource>"
140+
else:
141+
text = "<content>"
142+
143+
print(f"| Agent: {text}")
144+
145+
async def ext_method(self, method: str, params: dict) -> dict:
146+
raise RequestError.method_not_found(method)
147+
148+
async def ext_notification(self, method: str, params: dict) -> None:
149+
raise RequestError.method_not_found(method)
150+
151+
152+
async def read_console(prompt: str) -> str:
153+
"""Read input from console asynchronously."""
154+
loop = asyncio.get_running_loop()
155+
return await loop.run_in_executor(None, lambda: input(prompt))
156+
157+
158+
async def interactive_loop(conn, session_id: str) -> None:
159+
"""Run interactive prompt loop."""
160+
print("\nConnected to agent. Type messages (Ctrl+C or Ctrl+D to exit):\n")
161+
162+
while True:
163+
try:
164+
line = await read_console("> ")
165+
except EOFError:
166+
break
167+
except KeyboardInterrupt:
168+
print("", file=sys.stderr)
169+
break
170+
171+
if not line:
172+
continue
173+
174+
try:
175+
await conn.prompt(
176+
session_id=session_id,
177+
prompt=[text_block(line)],
178+
)
179+
except Exception as exc:
180+
logger.error("Prompt failed: %s", exc)
181+
182+
183+
async def main(argv: list[str]) -> int:
184+
"""Main entry point."""
185+
if len(argv) < 2:
186+
print("Usage: python examples/http_client.py WS_URL", file=sys.stderr)
187+
print("Example: python examples/http_client.py ws://localhost:8080/ws", file=sys.stderr)
188+
return 2
189+
190+
url = argv[1]
191+
logger.info("Connecting to %s", url)
192+
193+
# Connect to agent via WebSocket
194+
async with connect_http_agent(ExampleClient(), url) as conn:
195+
logger.info("Connected! Initializing protocol...")
196+
197+
# Initialize protocol
198+
await conn.initialize(
199+
protocol_version=PROTOCOL_VERSION,
200+
client_capabilities=ClientCapabilities(),
201+
client_info=Implementation(name="http-client", title="HTTP Client Example", version="0.1.0"),
202+
)
203+
204+
# Create new session
205+
import os
206+
207+
session = await conn.new_session(mcp_servers=[], cwd=os.getcwd())
208+
logger.info("Session created: %s", session.session_id)
209+
210+
# Run interactive loop
211+
await interactive_loop(conn, session.session_id)
212+
213+
logger.info("Connection closed")
214+
return 0
215+
216+
217+
if __name__ == "__main__":
218+
raise SystemExit(asyncio.run(main(sys.argv)))

0 commit comments

Comments
 (0)