试了 8 种方式全失败后我用双通道架构把 Kiro CLI 变成了 REST API我最近在做一件看起来不难但实际很折腾的事把 Kiro CLI 变成一个可以被其他服务调用的 REST API。Kiro CLI 是亚马逊云科技推出的终端 AI 编码工具。代码生成、分析、重构它都能干。但问题是——它只有 stdio 接口。你只能在终端里打字跟它聊天没有 HTTP 端口没有 SDK没有任何可以被程序化调用的方式。我想在自动化流水线里用它怎么办只能自己封装。最终产物两个文件约 600 行 Python先说结果免得大家觉得是个大工程kiro-api/ ├── acp_client.py # ACP JSON-RPC 2.0 客户端~300 行纯标准库 └── server.py # FastAPI HTTP 服务~280 行FastAPI 起在 8642 端口7 个 REST 端点支持多轮会话和模型换。完整实现代码见下文。搞清楚 ACP 协议Kiro CLI 有个隐藏的子命令kiro-cli acp。启动后通过 stdin/stdout 进行 JSON-RPC 2.0 双向通信这就是 ACPAgent Communication Protocol。主要方法方法方向作用initializeClient → Kiro握手session/newClient → Kiro创建会话session/promptClient → Kiro发任务session/updateKiro → Client流式推文本通知session/request_permissionKiro → Client敏感操作审批协议本身设计得不复杂。真正的坑在它没告诉你的事。模型切换8 种姿势全军覆没我想在 API 层支持动态切捡模型。按照正常思路应该在协议里指定。然而我的尝试惨烈结果session/new加 model 参数被直接忽略session/setModelMethod not foundsession/configureMethod not found_kiro.dev/commands/execute/model进程炸了反序列化错误kiro-cli acp --model Xunexpected argumentkiro-cli-chat acp --model Xunexpected argument环境变量KIRO_MODEL不认配置文件~/.kiro/settings/cli.json不认8 种方式无一幸免。我当时的心情可以用差点捠桌子来形容。分析了一圈发现ACP v1.25 协议设计上就不支持运行时切捡模型。这是协议层的限制不是我用法不对。但还有一条路kiro-cli chat --model X --no-interactive。这个命令支持指定模型代价是只能一次性运行不能多轮对话。破局双通道架构一条路搞不定两个需求那就开两条路。HTTP 客户端 | | REST API (port 8642) v FastAPI Server | ---------------- | | | modelauto | model指定 v v ACP 通道 Chat 通道 (常驻进程) (一次性子进程) 多轮会话 单次调用 JSON-RPC 2.0 文本解析ACP 通道Chat 通道模型不可选auto可指定多轮支持不支持冷启动无~3 秒场景日常多轮对话指定模型的单次任务设计原则很简单能走 ACP 就走 ACP只有指定模型时才降级到 Chat。实现细节一异步变同步ACP 底层是异步 stdio。你往 stdin 写请求stdout 上可能先推来一堆通知然后才是你要的响应。我用 Event Pending Map 做了同步封装# 发请求注册等待 self._pending[req_id] (threading.Event(), [None, None]) # 写入 stdin self._proc.stdin.write(json_msg \n) # 阻塞等待 event.wait(timeout) # 读线程收到响应按 id 匹配唤醒 if msg[id] in self._pending: holder[0] msg[result] event.set()每个请求一个唯一 id读线程负责分发写线程阻塞等待。简单粗暴但有效。实现细节二文本块在通知里不在响应里这个坑花了我好几个小时。session/prompt的最终响应{jsonrpc: 2.0, id: 3, result: {stopReason: end_turn}}没有文本我反复检查了好几遍一度怀疑是不是哪里解析错了。实际内容是通过session/update通知逐块推过来的← {method: session/update, content: {text: python}} ← {method: session/update, content: {text: \ndef fibonacci(n):}} ← ...更多文本块... ← {id: 3, result: {stopReason: end_turn}}正确做法在列表里收集所有 chunk收到end_turn后 join 拼接。这个行为在官方文档里没写清楚。只能自己抓包发现。实现细节三不回复权限请求 永久挂死Kiro 执行文件写入、命令执行前会发权限请求{id: 99, method: session/request_permission, params: {toolCall: {title: Creating app.py}}}这是个带 id 的 request。不回复的话Kiro 永远等着请求就挂死了。Headless 模式下直接自动审批def _handle_permission_request(self, msg_id, params): self._send_response(msg_id, {optionId: allow_always})实现细节四Chat 输出清洗Chat 通道的 stdout 是给人看的终端输出混着各种 UI 元素ASCII 艺术 bannerDid you know? 提示框Model/Plan 信息行前缀的实际回复 ← 目标内容▸ Credits: 0.09页脚清洗步骤正则去 ANSI 转义码跳过 banner 和提示框找标记提取内容遇▸ Credits截止这种文本解析确实脆弱CLI 版本更新后格式可能变。但目前没有更好的办法。实现细节五别忘了排空 stderrKiro CLI 的 stderr 也有输出。如果只读 stdout 不管 stderr缓冲区满了整个进程就阻塞。必须开一个线程专门排空def _drain_stderr(self): for line in self._proc.stderr: pass # 读掉就行这个问题在高频输出时特别容易触发。REST API 端点一览最终 FastAPI 服务在 8642 端口提供 7 个端点接口方法说明/promptPOST快捷调用支持指定模型/sessionsPOST创建多轮会话/sessions/{id}/promptPOST会话内发送任务/sessionsGET列出所有活跃会话/sessions/{id}DELETE删除指定会话/modelsGET列出可用模型/GET健康检查接口设计很标准的 RESTful 风格。/prompt是最常用的大部分场景直接用这个就行。需要多轮对话时才用 sessions 相关接口。完整请求生命周期把上面的细节串起来一次完整的 ACP 请求是这样的客户端发POST /promptFastAPI 检测 modelauto走 ACP 通道acp_client 调session/new拿到 session_id构造 JSON-RPC 请求写入 stdinKiro 处理任务通过session/update推文本块如果有敏感操作Kiro 发权限请求acp_client 自动审批Kiro 发最终响应{stopReason: end_turn}acp_client 拼接所有文本块FastAPI 返回 JSON 给客户端Chat 通道更简单客户端发POST /prompt指定模型FastAPI 检测 model 不是 auto走 Chat 通道启动子进程kiro-cli chat --model X --no-interactive等进程结束拿到 stdout清洗输出提取有效文本返回结果调用示例笀单演示一下怎么用# 快速提问走 ACP 通道 curl -X POST http://localhost:8642/prompt \ -H Content-Type: application/json \ -d {prompt: 写一个 Python 快速排序} # 指定模型走 Chat 通道 curl -X POST http://localhost:8642/prompt \ -H Content-Type: application/json \ -d {prompt: 优化这段 SQL, model: claude-sonnet-4} # 多轮对话 SESSION$(curl -s -X POST http://localhost:8642/sessions | jq -r .session_id) curl -X POST http://localhost:8642/sessions/$SESSION/prompt \ -d {prompt: 帮我写个 TODO 应用的后端} curl -X POST http://localhost:8642/sessions/$SESSION/prompt \ -d {prompt: 加上用户认证功能}局限性坦率说几个局限无鉴权API 没做认证只适合内网环境千万别暴露到公网单 ACP 连接高并发请求需要排队处理不适合大规模并发场景会话不持久化服务重启后所有会话丢失需要重新创建Chat 通道无上下文每次都是新进程不支持多轮对话输出清洗脆弱Chat 通道的文本解析依赖输出格式CLI 更新可能导致解析失败这亙限制在个人开发和小团队场景下完全可以接受。如果要上生产环境建议加上 API 鉴权、请求队列和会话持久化。最后说两句把 CLI 封装成 API技术上不复杂难的是摸清协议的实际行为。文档没覆盖的部分只能靠调试和抓包。这次最大的教训别假设协议的行为和你想的一样。试了 8 种模型切换方式全失败最后只能接受现实用双通道绕过去。