acp.go 36 KB

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