acp.go 27 KB

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