acp.go 32 KB

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