11#!/usr/bin/env python3
2- """Fake Kimi ACP agent for end-to-end tests.
2+ """Fake Kimi ACP agent for end-to-end and lifecycle-hardening tests.
33
4- Speaks newline-delimited JSON-RPC on stdio exactly like `kimi acp`:
5- answers `initialize`, `session/new`, `session/list`, `session/fork`,
6- `session/load`, `session/resume`, `session/close`, `session/delete`,
7- `authenticate`, `logout`, `session/set_model`, `session/set_mode`; on
8- `session/prompt` streams a few `session/update` notifications (one unknown
9- kind, one agent_message_chunk, another agent_message_chunk) and answers with
10- stop_reason `end_turn`. `session/cancel` notifications are ignored.
4+ Optional first argument selects behavior:
5+ normal normal ACP replies
6+ delay-prompt hold a prompt open long enough to test same-session admission
7+ malformed-prompt emit malformed JSON then stay alive
8+ exit-on-prompt exit the process while a prompt is pending
9+ hang-list never answer session/list
1110"""
1211import json
1312import sys
13+ import time
14+
15+
16+ MODE = sys .argv [1 ] if len (sys .argv ) > 1 else "normal"
1417
1518
1619def send (payload ):
17- sys .stdout .write (json .dumps (payload ) + "\n " )
20+ sys .stdout .write (json .dumps (payload , ensure_ascii = False ) + "\n " )
1821 sys .stdout .flush ()
1922
2023
2124def reply (req_id , result ):
2225 send ({"jsonrpc" : "2.0" , "id" : req_id , "result" : result })
2326
2427
28+ def send_normal_prompt (session_id , req_id ):
29+ send ({"jsonrpc" : "2.0" , "method" : "session/update" , "params" : {
30+ "sessionId" : session_id ,
31+ "update" : {"sessionUpdate" : "tool_call" , "title" : "ignored" },
32+ }})
33+ send ({"jsonrpc" : "2.0" , "method" : "session/update" , "params" : {
34+ "sessionId" : session_id ,
35+ "update" : {"sessionUpdate" : "agent_message_chunk" ,
36+ "content" : {"type" : "text" , "text" : "你好" }},
37+ }})
38+ send ({"jsonrpc" : "2.0" , "method" : "session/update" , "params" : {
39+ "sessionId" : session_id ,
40+ "update" : {"sessionUpdate" : "agent_message_chunk" ,
41+ "content" : {"type" : "text" , "text" : "世界" }},
42+ }})
43+ reply (req_id , {"stopReason" : "end_turn" })
44+
45+
2546def main ():
2647 for line in sys .stdin :
2748 line = line .strip ()
@@ -31,6 +52,7 @@ def main():
3152 frame = json .loads (line )
3253 except ValueError :
3354 continue
55+
3456 method = frame .get ("method" , "" )
3557 req_id = frame .get ("id" )
3658 params = frame .get ("params" ) or {}
@@ -48,26 +70,24 @@ def main():
4870 reply (req_id , {"sessionId" : params .get ("sessionId" , "sess_fake" )})
4971 elif method == "session/fork" :
5072 reply (req_id , {"sessionId" : "sess_forked" })
73+ elif method == "session/list" and MODE == "hang-list" :
74+ time .sleep (5 )
5175 elif method in ("session/list" , "session/set_mode" , "session/set_model" ,
5276 "authenticate" , "logout" , "session/close" , "session/delete" ):
5377 reply (req_id , {})
5478 elif method == "session/prompt" :
5579 session_id = params .get ("sessionId" , "sess_fake" )
56- send ({"jsonrpc" : "2.0" , "method" : "session/update" , "params" : {
57- "sessionId" : session_id ,
58- "update" : {"sessionUpdate" : "tool_call" , "title" : "ignored" },
59- }})
60- send ({"jsonrpc" : "2.0" , "method" : "session/update" , "params" : {
61- "sessionId" : session_id ,
62- "update" : {"sessionUpdate" : "agent_message_chunk" ,
63- "content" : {"type" : "text" , "text" : "你好" }},
64- }})
65- send ({"jsonrpc" : "2.0" , "method" : "session/update" , "params" : {
66- "sessionId" : session_id ,
67- "update" : {"sessionUpdate" : "agent_message_chunk" ,
68- "content" : {"type" : "text" , "text" : "世界" }},
69- }})
70- reply (req_id , {"stopReason" : "end_turn" })
80+ if MODE == "delay-prompt" :
81+ time .sleep (3 )
82+ send_normal_prompt (session_id , req_id )
83+ elif MODE == "malformed-prompt" :
84+ sys .stdout .write ("{not-json\n " )
85+ sys .stdout .flush ()
86+ time .sleep (5 )
87+ elif MODE == "exit-on-prompt" :
88+ sys .exit (7 )
89+ else :
90+ send_normal_prompt (session_id , req_id )
7191 elif method == "session/cancel" :
7292 pass
7393 elif req_id is not None :
0 commit comments