acp.go 32 KB

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