acp.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  1. // Package acp 实现 ACP (Agent Client Protocol) 客户端
  2. // 基于 ACP v1 规范,通过 stdin/stdout ND-JSON 与 Agent 子进程通讯
  3. package acp
  4. import (
  5. "bufio"
  6. "encoding/json"
  7. "fmt"
  8. "io"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "sync"
  13. "sync/atomic"
  14. "go.uber.org/zap"
  15. )
  16. // ============ JSON-RPC 2.0 基础类型 ============
  17. // Request JSON-RPC 请求
  18. type Request struct {
  19. JSONRPC string `json:"jsonrpc"`
  20. ID int64 `json:"id"`
  21. Method string `json:"method"`
  22. Params any `json:"params,omitempty"`
  23. }
  24. // Response JSON-RPC 响应
  25. type Response struct {
  26. JSONRPC string `json:"jsonrpc"`
  27. ID *int64 `json:"id,omitempty"`
  28. Method string `json:"method,omitempty"`
  29. Result json.RawMessage `json:"result,omitempty"`
  30. Error *RPCError `json:"error,omitempty"`
  31. Params json.RawMessage `json:"params,omitempty"`
  32. }
  33. // RPCError JSON-RPC 错误
  34. type RPCError struct {
  35. Code int `json:"code"`
  36. Message string `json:"message"`
  37. }
  38. // ============ ACP 协议类型 ============
  39. // InitializeParams 初始化请求参数
  40. type InitializeParams struct {
  41. ProtocolVersion int `json:"protocolVersion"`
  42. ClientInfo ClientInfo `json:"clientInfo"`
  43. ClientCapabilities ClientCapabilities `json:"clientCapabilities"`
  44. }
  45. // ClientInfo 客户端信息
  46. type ClientInfo struct {
  47. Name string `json:"name"`
  48. Version string `json:"version"`
  49. }
  50. // ClientCapabilities 客户端能力声明
  51. type ClientCapabilities struct {
  52. FS *FSCapabilities `json:"fs,omitempty"`
  53. Terminal bool `json:"terminal,omitempty"`
  54. }
  55. // FSCapabilities 文件系统能力
  56. type FSCapabilities struct {
  57. ReadTextFile bool `json:"readTextFile"`
  58. WriteTextFile bool `json:"writeTextFile"`
  59. }
  60. // NewSessionParams 创建会话参数
  61. type NewSessionParams struct {
  62. CWD string `json:"cwd"`
  63. MCPServers []any `json:"mcpServers"`
  64. }
  65. // NewSessionResult 创建会话响应
  66. type NewSessionResult struct {
  67. SessionId string `json:"sessionId"`
  68. Modes *SessionModes `json:"modes,omitempty"`
  69. }
  70. // SessionModes 会话模式
  71. type SessionModes struct {
  72. CurrentModeId string `json:"currentModeId"`
  73. AvailableModes []SessionMode `json:"availableModes"`
  74. }
  75. // SessionMode 单个模式
  76. type SessionMode struct {
  77. Id string `json:"id"`
  78. Name string `json:"name"`
  79. Description string `json:"description"`
  80. Meta *SessionMeta `json:"_meta,omitempty"`
  81. }
  82. // SessionMeta 模式元数据
  83. type SessionMeta struct {
  84. WelcomeMessage string `json:"welcomeMessage"`
  85. }
  86. // PromptParams 发送消息参数
  87. type PromptParams struct {
  88. SessionId string `json:"sessionId"`
  89. Prompt []ContentBlock `json:"prompt"`
  90. }
  91. // ContentBlock 内容块
  92. type ContentBlock struct {
  93. Type string `json:"type"`
  94. Text string `json:"text"`
  95. }
  96. // PromptResult prompt 响应
  97. type PromptResult struct {
  98. StopReason string `json:"stopReason"`
  99. }
  100. // CancelParams 取消参数
  101. type CancelParams struct {
  102. SessionId string `json:"sessionId"`
  103. }
  104. // ============ Session Update 事件类型 ============
  105. // SessionUpdate 会话更新通知
  106. type SessionUpdate struct {
  107. SessionId string `json:"sessionId"`
  108. Update json.RawMessage `json:"update"`
  109. }
  110. // UpdateBase 更新基础类型
  111. type UpdateBase struct {
  112. SessionUpdate string `json:"sessionUpdate"`
  113. }
  114. // AgentMessageChunk Agent 文本流式输出
  115. type AgentMessageChunk struct {
  116. SessionUpdate string `json:"sessionUpdate"` // "agent_message_chunk"
  117. MessageId string `json:"messageId,omitempty"`
  118. Content ContentBlock `json:"content"`
  119. }
  120. // ToolCall 工具调用通知
  121. type ToolCall struct {
  122. SessionUpdate string `json:"sessionUpdate"` // "tool_call"
  123. ToolCallId string `json:"toolCallId"`
  124. Title string `json:"title"`
  125. Status string `json:"status"` // pending, running, completed, failed
  126. }
  127. // ToolCallUpdate 工具调用状态更新
  128. type ToolCallUpdate struct {
  129. SessionUpdate string `json:"sessionUpdate"` // "tool_call_update"
  130. ToolCallId string `json:"toolCallId"`
  131. Title string `json:"title,omitempty"`
  132. Status string `json:"status"`
  133. }
  134. // ============ 客户端事件(对外输出) ============
  135. // Event 输出给上层的事件
  136. type Event struct {
  137. Type string // "text", "tool_call", "tool_update", "done", "error", "permission", "file_created"
  138. Content string
  139. ToolName string
  140. ToolStatus string
  141. SessionId string
  142. StopReason string
  143. // 权限请求相关
  144. Permission *PermissionEvent `json:"permission,omitempty"`
  145. // 文件创建相关
  146. File *FileRecord `json:"file,omitempty"`
  147. }
  148. // PermissionEvent 权限请求事件(推给前端)
  149. type PermissionEvent struct {
  150. RequestId string `json:"requestId"` // 用于前端回传
  151. Title string `json:"title"` // 权限标题
  152. Options []PermissionOption `json:"options"` // 可选选项
  153. }
  154. // PermissionOption 权限选项
  155. type PermissionOption struct {
  156. OptionId string `json:"optionId"`
  157. Name string `json:"name"`
  158. Kind string `json:"kind"` // allow_once, allow_always, deny
  159. }
  160. // ============ ACP Client 实现 ============
  161. // Client ACP 客户端(管理单个 Agent 进程)
  162. type Client struct {
  163. cmd *exec.Cmd
  164. stdin io.WriteCloser
  165. scanner *bufio.Scanner
  166. sessionId string
  167. requestId int64
  168. mu sync.Mutex // 保护 stdin 写入
  169. readMu sync.Mutex // 保护 stdout 读取
  170. // 权限请求等待机制
  171. permissionCh chan string // 接收前端选择的 optionId
  172. eventCh chan<- Event // 当前 prompt 的事件通道(用于推送 permission 事件)
  173. // 文件存储
  174. FileStore *FileStore
  175. UserId string
  176. // 文本累积(用于合并日志输出)
  177. textBuffer string
  178. }
  179. // ClientConfig 客户端配置
  180. type ClientConfig struct {
  181. Command string // Agent 命令(如 "kiro-cli")
  182. Args []string // 命令参数(如 ["acp"])
  183. WorkingDir string // 工作目录
  184. Env []string // 额外环境变量(如 "HOME=/home/user")
  185. }
  186. // NewClient 创建并启动 ACP Client
  187. func NewClient(cfg ClientConfig) (*Client, error) {
  188. cmd := exec.Command(cfg.Command, cfg.Args...)
  189. // 确保工作目录是绝对路径
  190. absDir, err := filepath.Abs(cfg.WorkingDir)
  191. if err != nil {
  192. absDir = cfg.WorkingDir
  193. }
  194. cmd.Dir = absDir
  195. // 设置环境变量
  196. cmd.Env = append(os.Environ(), cfg.Env...)
  197. stdin, err := cmd.StdinPipe()
  198. if err != nil {
  199. return nil, fmt.Errorf("创建 stdin pipe 失败: %w", err)
  200. }
  201. stdout, err := cmd.StdoutPipe()
  202. if err != nil {
  203. return nil, fmt.Errorf("创建 stdout pipe 失败: %w", err)
  204. }
  205. // 捕获 stderr
  206. stderrPipe, _ := cmd.StderrPipe()
  207. go func() {
  208. if stderrPipe != nil {
  209. s := bufio.NewScanner(stderrPipe)
  210. for s.Scan() {
  211. zap.L().Warn("[ACP] agent stderr", zap.String("line", s.Text()))
  212. }
  213. }
  214. }()
  215. if err := cmd.Start(); err != nil {
  216. return nil, fmt.Errorf("启动 Agent 进程失败: %w", err)
  217. }
  218. scanner := bufio.NewScanner(stdout)
  219. scanner.Buffer(make([]byte, 0, 2*1024*1024), 2*1024*1024) // 2MB buffer
  220. return &Client{
  221. cmd: cmd,
  222. stdin: stdin,
  223. scanner: scanner,
  224. permissionCh: make(chan string, 1),
  225. FileStore: NewFileStore(),
  226. }, nil
  227. }
  228. // ResolvePermission 前端回传权限选择结果
  229. func (c *Client) ResolvePermission(optionId string) {
  230. select {
  231. case c.permissionCh <- optionId:
  232. default:
  233. // 没有等待中的权限请求,忽略
  234. }
  235. }
  236. // Initialize 执行初始化握手
  237. func (c *Client) Initialize() error {
  238. params := InitializeParams{
  239. ProtocolVersion: 1,
  240. ClientInfo: ClientInfo{
  241. Name: "fwh-toolbox",
  242. Version: "1.0.0",
  243. },
  244. ClientCapabilities: ClientCapabilities{
  245. FS: &FSCapabilities{
  246. ReadTextFile: true,
  247. WriteTextFile: true,
  248. },
  249. Terminal: false, // 不声明终端能力,强制 Agent 使用 writeTextFile
  250. },
  251. }
  252. _, err := c.sendAndWait("initialize", params)
  253. return err
  254. }
  255. // NewSession 创建新会话,返回 sessionId 和 welcomeMessage
  256. func (c *Client) NewSession(cwd string) (string, string, error) {
  257. params := NewSessionParams{
  258. CWD: cwd,
  259. MCPServers: []any{},
  260. }
  261. result, err := c.sendAndWait("session/new", params)
  262. if err != nil {
  263. return "", "", err
  264. }
  265. var res NewSessionResult
  266. if err := json.Unmarshal(result, &res); err != nil {
  267. return "", "", fmt.Errorf("解析 session/new 响应失败: %w", err)
  268. }
  269. c.sessionId = res.SessionId
  270. // 提取当前 mode 的 welcomeMessage
  271. var welcomeMsg string
  272. if res.Modes != nil {
  273. for _, mode := range res.Modes.AvailableModes {
  274. if mode.Id == res.Modes.CurrentModeId && mode.Meta != nil {
  275. welcomeMsg = mode.Meta.WelcomeMessage
  276. break
  277. }
  278. }
  279. }
  280. return res.SessionId, welcomeMsg, nil
  281. }
  282. // ReplayMessage 回放的单条消息
  283. type ReplayMessage struct {
  284. Role string `json:"role"` // "user" 或 "assistant"
  285. Content string `json:"content"`
  286. MessageId string `json:"messageId,omitempty"`
  287. }
  288. // LoadSession 加载已有会话(恢复历史对话上下文),返回回放的消息列表
  289. func (c *Client) LoadSession(sessionId string, cwd string) (string, []ReplayMessage, error) {
  290. params := map[string]any{
  291. "sessionId": sessionId,
  292. "cwd": cwd,
  293. "mcpServers": []any{},
  294. }
  295. // 发送 session/load 请求
  296. id := c.sendRequest("session/load", params)
  297. // 收集回放消息
  298. var replayMessages []ReplayMessage
  299. currentMsg := &ReplayMessage{}
  300. // 读取回放事件直到收到 session/load 的 response
  301. for c.scanner.Scan() {
  302. line := c.scanner.Text()
  303. if line == "" {
  304. continue
  305. }
  306. var msg struct {
  307. JSONRPC string `json:"jsonrpc"`
  308. ID json.RawMessage `json:"id,omitempty"`
  309. Method string `json:"method,omitempty"`
  310. Result json.RawMessage `json:"result,omitempty"`
  311. Error *RPCError `json:"error,omitempty"`
  312. Params json.RawMessage `json:"params,omitempty"`
  313. }
  314. if err := json.Unmarshal([]byte(line), &msg); err != nil {
  315. continue
  316. }
  317. // 检查是否是 session/load 的响应
  318. hasId := len(msg.ID) > 0 && string(msg.ID) != "null"
  319. if hasId && msg.Method == "" {
  320. var numId int64
  321. json.Unmarshal(msg.ID, &numId)
  322. if numId == id {
  323. // session/load 完成
  324. if msg.Error != nil {
  325. return "", nil, fmt.Errorf("session/load 错误: %s", msg.Error.Message)
  326. }
  327. c.sessionId = sessionId
  328. // 追加最后一条未关闭的消息
  329. if currentMsg.Content != "" {
  330. replayMessages = append(replayMessages, *currentMsg)
  331. }
  332. return sessionId, replayMessages, nil
  333. }
  334. }
  335. // 处理回放的 session/update 通知
  336. if msg.Method == "session/update" || msg.Method == "_kiro.dev/session/update" {
  337. var notif SessionUpdate
  338. if err := json.Unmarshal(msg.Params, &notif); err != nil {
  339. continue
  340. }
  341. var base UpdateBase
  342. if err := json.Unmarshal(notif.Update, &base); err != nil {
  343. continue
  344. }
  345. switch base.SessionUpdate {
  346. case "user_message_chunk":
  347. // 新的用户消息 → 保存上一条并开始新消息
  348. if currentMsg.Content != "" {
  349. replayMessages = append(replayMessages, *currentMsg)
  350. }
  351. var chunk AgentMessageChunk
  352. json.Unmarshal(notif.Update, &chunk)
  353. currentMsg = &ReplayMessage{Role: "user", Content: chunk.Content.Text, MessageId: chunk.MessageId}
  354. case "agent_message_chunk":
  355. // AI 消息
  356. var chunk AgentMessageChunk
  357. json.Unmarshal(notif.Update, &chunk)
  358. if currentMsg.Role != "assistant" || (chunk.MessageId != "" && chunk.MessageId != currentMsg.MessageId) {
  359. // 新的 AI 消息 → 保存上一条
  360. if currentMsg.Content != "" {
  361. replayMessages = append(replayMessages, *currentMsg)
  362. }
  363. currentMsg = &ReplayMessage{Role: "assistant", Content: chunk.Content.Text, MessageId: chunk.MessageId}
  364. } else {
  365. // 同一条消息的续写
  366. currentMsg.Content += chunk.Content.Text
  367. }
  368. case "agent_thought_chunk":
  369. // 思考内容忽略(不显示给用户)
  370. case "tool_call", "tool_call_update", "tool_call_chunk":
  371. // 工具调用回放忽略
  372. default:
  373. // 其他类型忽略
  374. }
  375. continue
  376. }
  377. // 忽略其他通知(kiro 扩展等)
  378. }
  379. return "", nil, fmt.Errorf("session/load 未收到响应(进程可能已退出)")
  380. }
  381. // Prompt 发送消息并流式读取事件
  382. // eventCh 会在 turn 结束或出错时关闭
  383. func (c *Client) Prompt(sessionId string, message string, eventCh chan<- Event) {
  384. defer close(eventCh)
  385. // 设置当前事件通道(供 handleAgentRequest 推送 permission 事件)
  386. c.eventCh = eventCh
  387. params := PromptParams{
  388. SessionId: sessionId,
  389. Prompt: []ContentBlock{
  390. {Type: "text", Text: message},
  391. },
  392. }
  393. // 发送 prompt 请求
  394. promptId := c.sendRequest("session/prompt", params)
  395. zap.L().Debug("[ACP] prompt 已发送", zap.Int64("promptId", promptId), zap.String("sessionId", sessionId))
  396. // 持续读取直到收到 prompt 的 response
  397. for c.scanner.Scan() {
  398. line := c.scanner.Text()
  399. if line == "" {
  400. continue
  401. }
  402. // 用宽松结构解析,兼容所有消息格式
  403. var msg struct {
  404. JSONRPC string `json:"jsonrpc"`
  405. ID json.RawMessage `json:"id,omitempty"`
  406. Method string `json:"method,omitempty"`
  407. Result json.RawMessage `json:"result,omitempty"`
  408. Error *RPCError `json:"error,omitempty"`
  409. Params json.RawMessage `json:"params,omitempty"`
  410. }
  411. if err := json.Unmarshal([]byte(line), &msg); err != nil {
  412. zap.L().Warn("[ACP] JSON解析失败,跳过", zap.String("line", line[:min(len(line), 100)]))
  413. continue
  414. }
  415. // 解析 ID(兼容 int64 和 string)
  416. hasId := len(msg.ID) > 0 && string(msg.ID) != "null"
  417. var numericId int64
  418. isPromptResp := false
  419. if hasId {
  420. json.Unmarshal(msg.ID, &numericId)
  421. isPromptResp = numericId == promptId && msg.Method == ""
  422. }
  423. // 收到 prompt 的响应 → turn 结束
  424. if isPromptResp {
  425. zap.L().Debug("[ACP] 收到 prompt 响应(turn结束)", zap.Int64("id", numericId))
  426. // 打印本轮完整回复
  427. if c.textBuffer != "" {
  428. zap.L().Info("[ACP] AI 回复完整内容", zap.String("text", c.textBuffer))
  429. c.textBuffer = ""
  430. }
  431. var result PromptResult
  432. if msg.Error != nil {
  433. eventCh <- Event{Type: "error", Content: msg.Error.Message}
  434. } else if err := json.Unmarshal(msg.Result, &result); err == nil {
  435. eventCh <- Event{Type: "done", SessionId: sessionId, StopReason: result.StopReason}
  436. }
  437. return
  438. }
  439. // 处理 notification (session/update 或 _kiro.dev/session/update)
  440. if !hasId && (msg.Method == "session/update" || msg.Method == "_kiro.dev/session/update") {
  441. c.handleSessionUpdate(msg.Params, eventCh)
  442. continue
  443. }
  444. // 处理 Agent→Client 反向请求(有 ID + Method = 请求)
  445. if hasId && msg.Method != "" {
  446. zap.L().Debug("[ACP] 收到 Agent 反向请求", zap.String("method", msg.Method), zap.String("id", string(msg.ID)))
  447. c.handleAgentRequest(msg.ID, msg.Method, msg.Params)
  448. continue
  449. }
  450. // 未匹配的消息(kiro扩展通知等)
  451. if msg.Method != "" {
  452. c.handleExtensionNotification(msg.Method, msg.Params, eventCh)
  453. } else {
  454. zap.L().Debug("[ACP] 未处理的消息", zap.String("line", line[:min(len(line), 200)]))
  455. }
  456. }
  457. // Scanner 结束说明进程退出
  458. eventCh <- Event{Type: "error", Content: "Agent 进程已断开"}
  459. }
  460. // Cancel 取消当前操作
  461. func (c *Client) Cancel(sessionId string) {
  462. c.sendRequest("session/cancel", CancelParams{SessionId: sessionId})
  463. }
  464. // GetSessionId 获取当前会话 ID
  465. func (c *Client) GetSessionId() string {
  466. return c.sessionId
  467. }
  468. // Close 关闭 ACP Client,终止 Agent 进程
  469. func (c *Client) Close() {
  470. // 关闭 stdin 通知 Agent 退出
  471. c.stdin.Close()
  472. // 终止进程
  473. if c.cmd.Process != nil {
  474. c.cmd.Process.Kill()
  475. // Wait 回收子进程资源,防止僵尸进程
  476. go c.cmd.Wait()
  477. }
  478. // 解除可能阻塞在 permissionCh 上的 goroutine
  479. select {
  480. case c.permissionCh <- "deny":
  481. default:
  482. }
  483. // 清理 FileStore
  484. if c.FileStore != nil {
  485. c.FileStore.mu.Lock()
  486. c.FileStore.files = nil
  487. c.FileStore.mu.Unlock()
  488. }
  489. }
  490. // IsAlive 检查 Agent 进程是否存活
  491. func (c *Client) IsAlive() bool {
  492. return c.cmd.ProcessState == nil
  493. }
  494. // ============ 内部方法 ============
  495. // sendRequest 发送请求(不等待响应),返回 request ID
  496. func (c *Client) sendRequest(method string, params any) int64 {
  497. c.mu.Lock()
  498. defer c.mu.Unlock()
  499. id := atomic.AddInt64(&c.requestId, 1)
  500. req := Request{
  501. JSONRPC: "2.0",
  502. ID: id,
  503. Method: method,
  504. Params: params,
  505. }
  506. data, err := json.Marshal(req)
  507. if err != nil {
  508. zap.L().Error("[ACP] 序列化请求失败", zap.Error(err))
  509. return id
  510. }
  511. data = append(data, '\n')
  512. if _, err := c.stdin.Write(data); err != nil {
  513. zap.L().Error("[ACP] 写入 stdin 失败", zap.String("method", method), zap.Error(err))
  514. }
  515. return id
  516. }
  517. // sendAndWait 发送请求并等待对应 ID 的响应
  518. func (c *Client) sendAndWait(method string, params any) (json.RawMessage, error) {
  519. id := c.sendRequest(method, params)
  520. // 读取响应(跳过 notification 和其他消息)
  521. for c.scanner.Scan() {
  522. line := c.scanner.Text()
  523. if line == "" {
  524. continue
  525. }
  526. var resp Response
  527. if err := json.Unmarshal([]byte(line), &resp); err != nil {
  528. continue
  529. }
  530. // 匹配 ID 的响应
  531. if resp.ID != nil && *resp.ID == id {
  532. if resp.Error != nil {
  533. return nil, fmt.Errorf("ACP 错误 [%d]: %s", resp.Error.Code, resp.Error.Message)
  534. }
  535. return resp.Result, nil
  536. }
  537. // 跳过 notification(session/new 之前可能收到 kiro 扩展通知)
  538. }
  539. return nil, fmt.Errorf("未收到 %s 的响应(进程可能已退出)", method)
  540. }
  541. // handleSessionUpdate 解析并分发 session/update 通知
  542. func (c *Client) handleSessionUpdate(params json.RawMessage, eventCh chan<- Event) {
  543. var notif SessionUpdate
  544. if err := json.Unmarshal(params, &notif); err != nil {
  545. return
  546. }
  547. // 解析 update 的类型
  548. var base UpdateBase
  549. if err := json.Unmarshal(notif.Update, &base); err != nil {
  550. return
  551. }
  552. // zap.L().Debug("[ACP] session/update", zap.String("type", base.SessionUpdate))
  553. switch base.SessionUpdate {
  554. case "agent_message_chunk":
  555. var chunk AgentMessageChunk
  556. if err := json.Unmarshal(notif.Update, &chunk); err == nil {
  557. c.textBuffer += chunk.Content.Text
  558. eventCh <- Event{Type: "text", Content: chunk.Content.Text}
  559. }
  560. case "agent_thought_chunk":
  561. var chunk AgentMessageChunk
  562. if err := json.Unmarshal(notif.Update, &chunk); err == nil {
  563. c.textBuffer += chunk.Content.Text
  564. eventCh <- Event{Type: "text", Content: chunk.Content.Text}
  565. }
  566. case "tool_call":
  567. var tc ToolCall
  568. if err := json.Unmarshal(notif.Update, &tc); err == nil {
  569. zap.L().Debug("[ACP] → tool_call", zap.String("title", tc.Title), zap.String("status", tc.Status), zap.String("id", tc.ToolCallId))
  570. eventCh <- Event{Type: "tool_call", ToolName: tc.Title, ToolStatus: tc.Status}
  571. }
  572. case "tool_call_update":
  573. var tcu ToolCallUpdate
  574. if err := json.Unmarshal(notif.Update, &tcu); err == nil {
  575. zap.L().Debug("[ACP] → tool_call_update", zap.String("title", tcu.Title), zap.String("status", tcu.Status), zap.String("id", tcu.ToolCallId))
  576. eventCh <- Event{Type: "tool_update", ToolName: tcu.Title, ToolStatus: tcu.Status}
  577. }
  578. case "tool_call_chunk":
  579. // 工具调用的流式参数输出(Kiro 扩展)
  580. zap.L().Debug("[ACP] → tool_call_chunk (ignored)")
  581. case "plan":
  582. zap.L().Debug("[ACP] → plan update")
  583. default:
  584. zap.L().Debug("[ACP] → 未知 session/update 类型", zap.String("type", base.SessionUpdate))
  585. }
  586. }
  587. // handleExtensionNotification 处理 Kiro 扩展通知
  588. func (c *Client) handleExtensionNotification(method string, params json.RawMessage, eventCh chan<- Event) {
  589. switch method {
  590. case "_kiro.dev/commands/available":
  591. // 可用命令和工具列表
  592. var data struct {
  593. SessionId string `json:"sessionId"`
  594. Commands []struct {
  595. Name string `json:"name"`
  596. Description string `json:"description"`
  597. } `json:"commands"`
  598. Tools []struct {
  599. Name string `json:"name"`
  600. Description string `json:"description"`
  601. Source string `json:"source"`
  602. } `json:"tools"`
  603. }
  604. if err := json.Unmarshal(params, &data); err == nil {
  605. zap.L().Debug("[ACP] 可用命令/工具更新",
  606. zap.Int("commands", len(data.Commands)),
  607. zap.Int("tools", len(data.Tools)),
  608. )
  609. // 推送工具列表给前端(可选)
  610. if len(data.Tools) > 0 {
  611. toolNames := make([]string, 0, len(data.Tools))
  612. for _, t := range data.Tools {
  613. toolNames = append(toolNames, t.Name)
  614. }
  615. eventCh <- Event{Type: "tools_available", Content: toJSONStr(toolNames)}
  616. }
  617. }
  618. case "_kiro.dev/metadata":
  619. // 上下文使用量和耗时
  620. var data struct {
  621. SessionId string `json:"sessionId"`
  622. ContextUsagePercentage float64 `json:"contextUsagePercentage"`
  623. TurnDurationMs int64 `json:"turnDurationMs,omitempty"`
  624. }
  625. if err := json.Unmarshal(params, &data); err == nil {
  626. if data.TurnDurationMs > 0 {
  627. zap.L().Debug("[ACP] metadata",
  628. zap.Float64("contextUsage%", data.ContextUsagePercentage),
  629. zap.Int64("turnMs", data.TurnDurationMs),
  630. )
  631. eventCh <- Event{
  632. Type: "metadata",
  633. Content: toJSONStr(map[string]any{
  634. "contextUsagePercent": data.ContextUsagePercentage,
  635. "turnDurationMs": data.TurnDurationMs,
  636. }),
  637. }
  638. }
  639. }
  640. case "_kiro.dev/subagent/list_update":
  641. // 子代理列表更新
  642. zap.L().Debug("[ACP] 子代理列表更新")
  643. case "_kiro.dev/mcp/server_initialized":
  644. // MCP Server 初始化完成
  645. var data struct {
  646. ServerName string `json:"serverName"`
  647. }
  648. json.Unmarshal(params, &data)
  649. zap.L().Info("[ACP] MCP Server 初始化完成", zap.String("server", data.ServerName))
  650. case "_kiro.dev/compaction/status":
  651. // 上下文压缩状态
  652. zap.L().Debug("[ACP] 上下文压缩中")
  653. case "_kiro.dev/clear/status":
  654. // 会话清除状态
  655. zap.L().Debug("[ACP] 会话清除中")
  656. default:
  657. zap.L().Debug("[ACP] 未处理的扩展通知", zap.String("method", method))
  658. }
  659. }
  660. // toJSONStr 序列化为 JSON 字符串
  661. func toJSONStr(v any) string {
  662. data, _ := json.Marshal(v)
  663. return string(data)
  664. }
  665. // handleAgentRequest 处理 Agent→Client 的反向请求
  666. func (c *Client) handleAgentRequest(rawId json.RawMessage, method string, rawParams json.RawMessage) {
  667. c.mu.Lock()
  668. defer c.mu.Unlock()
  669. zap.L().Debug("[ACP] 处理反向请求", zap.String("method", method), zap.String("id", string(rawId)), zap.String("params", string(rawParams[:min(len(rawParams), 200)])))
  670. var respData []byte
  671. switch method {
  672. case "readTextFile":
  673. // 读取文件:先从 FileStore 查找(MinIO),再从本地文件系统
  674. var params struct {
  675. Path string `json:"path"`
  676. Line *int `json:"line,omitempty"`
  677. Limit *int `json:"limit,omitempty"`
  678. }
  679. if err := json.Unmarshal(rawParams, &params); err == nil && params.Path != "" {
  680. var content string
  681. var readErr error
  682. // 优先从 FileStore(MinIO)读取
  683. if c.FileStore.HasFile(params.Path) {
  684. content, readErr = c.FileStore.ReadFile(params.Path)
  685. zap.L().Debug("[ACP] readTextFile 从 MinIO 读取", zap.String("path", params.Path))
  686. } else {
  687. // 回退到本地文件系统
  688. data, err := os.ReadFile(params.Path)
  689. if err == nil {
  690. content = string(data)
  691. zap.L().Debug("[ACP] readTextFile 从本地读取", zap.String("path", params.Path))
  692. } else {
  693. readErr = err
  694. }
  695. }
  696. if readErr == nil {
  697. result, _ := json.Marshal(map[string]string{"content": content})
  698. respData, _ = json.Marshal(map[string]any{
  699. "jsonrpc": "2.0",
  700. "id": rawId,
  701. "result": json.RawMessage(result),
  702. })
  703. }
  704. }
  705. if respData == nil {
  706. respData, _ = json.Marshal(map[string]any{
  707. "jsonrpc": "2.0",
  708. "id": rawId,
  709. "error": map[string]any{"code": -1, "message": "file not found"},
  710. })
  711. }
  712. case "writeTextFile":
  713. // 写入文件到 MinIO(不写本地)
  714. var params struct {
  715. Path string `json:"path"`
  716. Content string `json:"content"`
  717. }
  718. if err := json.Unmarshal(rawParams, &params); err == nil && params.Path != "" {
  719. record, err := c.FileStore.WriteFile(c.UserId, c.sessionId, params.Path, params.Content)
  720. if err == nil {
  721. respData, _ = json.Marshal(map[string]any{
  722. "jsonrpc": "2.0",
  723. "id": rawId,
  724. "result": map[string]any{},
  725. })
  726. // 推送 file_created 事件给前端
  727. if c.eventCh != nil {
  728. c.eventCh <- Event{Type: "file_created", File: record}
  729. }
  730. } else {
  731. zap.L().Error("[ACP] writeTextFile MinIO上传失败", zap.Error(err))
  732. }
  733. }
  734. if respData == nil {
  735. respData, _ = json.Marshal(map[string]any{
  736. "jsonrpc": "2.0",
  737. "id": rawId,
  738. "error": map[string]any{"code": -1, "message": "write failed"},
  739. })
  740. }
  741. case "requestPermission", "session/request_permission":
  742. // 解析权限请求参数
  743. var params struct {
  744. ToolCall *struct {
  745. Title string `json:"title"`
  746. } `json:"toolCall"`
  747. Options []struct {
  748. OptionId string `json:"optionId"`
  749. Name string `json:"name"`
  750. Kind string `json:"kind"`
  751. } `json:"options"`
  752. }
  753. json.Unmarshal(rawParams, &params)
  754. title := ""
  755. if params.ToolCall != nil {
  756. title = params.ToolCall.Title
  757. }
  758. // 判断是否涉及写操作(需要手动授权)
  759. isWriteOp := true
  760. // isWriteOp := false
  761. // if title != "" {
  762. // for _, keyword := range []string{"write", "Write", "create", "Create", "delete", "Delete", "修改", "写入", "创建", "删除"} {
  763. // if len(title) >= len(keyword) && containsStr(title, keyword) {
  764. // isWriteOp = true
  765. // break
  766. // }
  767. // }
  768. // }
  769. if isWriteOp && c.eventCh != nil {
  770. // 写操作:推送权限请求给前端,等待前端回复
  771. options := make([]PermissionOption, 0, len(params.Options))
  772. for _, opt := range params.Options {
  773. options = append(options, PermissionOption{
  774. OptionId: opt.OptionId,
  775. Name: opt.Name,
  776. Kind: opt.Kind,
  777. })
  778. }
  779. requestId := fmt.Sprintf("perm_%s", string(rawId))
  780. c.eventCh <- Event{
  781. Type: "permission",
  782. Permission: &PermissionEvent{
  783. RequestId: requestId,
  784. Title: title,
  785. Options: options,
  786. },
  787. }
  788. // 释放 mu 锁后等待前端回复(否则会死锁)
  789. c.mu.Unlock()
  790. selectedOptionId := <-c.permissionCh
  791. c.mu.Lock()
  792. respData, _ = json.Marshal(map[string]any{
  793. "jsonrpc": "2.0",
  794. "id": rawId,
  795. "result": map[string]any{
  796. "outcome": map[string]any{
  797. "outcome": "selected",
  798. "optionId": selectedOptionId,
  799. },
  800. },
  801. })
  802. } else {
  803. // 非写操作:自动允许
  804. respData, _ = json.Marshal(map[string]any{
  805. "jsonrpc": "2.0",
  806. "id": rawId,
  807. "result": map[string]any{
  808. "outcome": map[string]any{
  809. "outcome": "selected",
  810. "optionId": "allow_once",
  811. },
  812. },
  813. })
  814. }
  815. default:
  816. // 未知请求返回错误
  817. respData, _ = json.Marshal(map[string]any{
  818. "jsonrpc": "2.0",
  819. "id": rawId,
  820. "error": map[string]any{"code": -32601, "message": "method not supported"},
  821. })
  822. }
  823. if respData != nil {
  824. zap.L().Debug("[ACP] 发送反向响应", zap.String("method", method), zap.String("resp", string(respData[:min(len(respData), 200)])))
  825. respData = append(respData, '\n')
  826. c.stdin.Write(respData)
  827. } else {
  828. zap.L().Warn("[ACP] 反向请求无响应数据", zap.String("method", method))
  829. }
  830. }
  831. // containsStr 简单字符串包含检测
  832. func containsStr(s, substr string) bool {
  833. return len(s) >= len(substr) && (s == substr || len(s) > 0 && stringContains(s, substr))
  834. }
  835. func stringContains(s, substr string) bool {
  836. for i := 0; i <= len(s)-len(substr); i++ {
  837. if s[i:i+len(substr)] == substr {
  838. return true
  839. }
  840. }
  841. return false
  842. }
  843. func min(a, b int) int {
  844. if a < b {
  845. return a
  846. }
  847. return b
  848. }