acp.go 28 KB

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