You've already forked agentic-coding-workflow
160 lines
4.1 KiB
Go
160 lines
4.1 KiB
Go
// cmd/fake-acp-agent 是集成测试用的 fake ACP agent,stdin/stdout 走 JSON-RPC。
|
|
//
|
|
// 行为通过 env 控制:
|
|
//
|
|
// FAKE_ACP_HANG=true 永不响应任何请求(测 handshake 超时)
|
|
// FAKE_ACP_CRASH_AFTER_PROMPTS=N 处理完 N 个 prompt 后 os.Exit(1)
|
|
// FAKE_ACP_FS_READ=path prompt 时发起 fs/read_text_file 请求该路径
|
|
// FAKE_ACP_FS_WRITE=path:content prompt 时发起 fs/write_text_file
|
|
// FAKE_ACP_PROMPT_UPDATES=N 每 prompt 发 N 个 update(默认 3)
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
type Message struct {
|
|
JSONRPC string `json:"jsonrpc"`
|
|
ID json.RawMessage `json:"id,omitempty"`
|
|
Method string `json:"method,omitempty"`
|
|
Params json.RawMessage `json:"params,omitempty"`
|
|
Result json.RawMessage `json:"result,omitempty"`
|
|
Error *RPCError `json:"error,omitempty"`
|
|
}
|
|
|
|
type RPCError struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
var (
|
|
promptsHandled atomic.Int32
|
|
nextServerID atomic.Int64
|
|
)
|
|
|
|
func main() {
|
|
if os.Getenv("FAKE_ACP_HANG") == "true" {
|
|
select {} // 永不退出
|
|
}
|
|
|
|
in := bufio.NewReader(os.Stdin)
|
|
out := bufio.NewWriter(os.Stdout)
|
|
defer out.Flush()
|
|
|
|
for {
|
|
line, err := in.ReadBytes('\n')
|
|
if err != nil {
|
|
return
|
|
}
|
|
var msg Message
|
|
if err := json.Unmarshal(line, &msg); err != nil {
|
|
continue
|
|
}
|
|
switch msg.Method {
|
|
case "initialize":
|
|
respond(out, msg.ID, map[string]any{
|
|
"protocolVersion": 1,
|
|
"agentInfo": map[string]any{"name": "fake", "version": "0.0.0"},
|
|
})
|
|
case "session/new":
|
|
respond(out, msg.ID, map[string]any{"sessionId": "fake-session-id"})
|
|
case "session/prompt":
|
|
handlePrompt(out, msg.ID)
|
|
case "session/cancel":
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
func handlePrompt(out *bufio.Writer, id json.RawMessage) {
|
|
updates := 3
|
|
if v := os.Getenv("FAKE_ACP_PROMPT_UPDATES"); v != "" {
|
|
if n, _ := strconv.Atoi(v); n > 0 {
|
|
updates = n
|
|
}
|
|
}
|
|
for i := 0; i < updates; i++ {
|
|
notify(out, "session/update", map[string]any{
|
|
"sessionId": "fake-session-id",
|
|
"update": map[string]any{
|
|
"sessionUpdate": "agent_message_chunk",
|
|
"content": []map[string]any{{"type": "text", "text": fmt.Sprintf("chunk %d", i)}},
|
|
},
|
|
})
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
if path := os.Getenv("FAKE_ACP_FS_READ"); path != "" {
|
|
_ = doFsRead(out, path)
|
|
}
|
|
|
|
if spec := os.Getenv("FAKE_ACP_FS_WRITE"); spec != "" {
|
|
parts := strings.SplitN(spec, ":", 2)
|
|
if len(parts) == 2 {
|
|
_ = doFsWrite(out, parts[0], parts[1])
|
|
}
|
|
}
|
|
|
|
respond(out, id, map[string]any{"stopReason": "end_turn"})
|
|
|
|
if maxStr := os.Getenv("FAKE_ACP_CRASH_AFTER_PROMPTS"); maxStr != "" {
|
|
n, _ := strconv.Atoi(maxStr)
|
|
count := promptsHandled.Add(1)
|
|
if int(count) >= n {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func doFsRead(out *bufio.Writer, path string) error {
|
|
id := nextServerID.Add(1)
|
|
idJSON, _ := json.Marshal(id)
|
|
body := mustJSON(Message{
|
|
JSONRPC: "2.0", ID: idJSON, Method: "fs/read_text_file",
|
|
Params: mustJSON(map[string]any{"sessionId": "fake-session-id", "path": path}),
|
|
})
|
|
_, _ = out.Write(body)
|
|
_, _ = out.Write([]byte{'\n'})
|
|
_ = out.Flush()
|
|
return nil
|
|
}
|
|
|
|
func doFsWrite(out *bufio.Writer, path, content string) error {
|
|
id := nextServerID.Add(1)
|
|
idJSON, _ := json.Marshal(id)
|
|
body := mustJSON(Message{
|
|
JSONRPC: "2.0", ID: idJSON, Method: "fs/write_text_file",
|
|
Params: mustJSON(map[string]any{"sessionId": "fake-session-id", "path": path, "content": content}),
|
|
})
|
|
_, _ = out.Write(body)
|
|
_, _ = out.Write([]byte{'\n'})
|
|
_ = out.Flush()
|
|
return nil
|
|
}
|
|
|
|
func respond(out *bufio.Writer, id json.RawMessage, result any) {
|
|
body := mustJSON(Message{JSONRPC: "2.0", ID: id, Result: mustJSON(result)})
|
|
_, _ = out.Write(body)
|
|
_, _ = out.Write([]byte{'\n'})
|
|
_ = out.Flush()
|
|
}
|
|
|
|
func notify(out *bufio.Writer, method string, params any) {
|
|
body := mustJSON(Message{JSONRPC: "2.0", Method: method, Params: mustJSON(params)})
|
|
_, _ = out.Write(body)
|
|
_, _ = out.Write([]byte{'\n'})
|
|
_ = out.Flush()
|
|
}
|
|
|
|
func mustJSON(v any) json.RawMessage {
|
|
b, _ := json.Marshal(v)
|
|
return b
|
|
}
|