Kiro CLI 封装 REST API 实战:ACP 协议双通道架构设计与 FastAPI 集成详解(附完整源码)
Kiro CLI 封装 REST API 实战:ACP 协议双通道架构设计与 FastAPI 集成详解(附完整源码)
Kiro CLI 只有 stdio 接口,想让别的服务调它?门都没有。
我最近在搞一个自动化编码流水线,需要把亚马逊云科技的 Kiro CLI 集成进来。结果发现这玩意儿只能在终端里交互,没有 HTTP 接口,没有 SDK,连个 socket 都没有。说白了,它就是个"你打字我回答"的命令行工具。
但我就是要让它变成 REST API。折腾了一周,踩了不少坑,总算搞出来了。今天把整个过程分享出来。
先说结论:两个文件,600 行搞定
最终产出就两个文件:
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 有个子命令 kiro-cli acp,启动后通过 stdio 进行 JSON-RPC 2.0 双向通信。这就是 ACP(Agent Communication Protocol)协议。
核心方法就这几个:
| 方法 | 方向 | 干嘛的 |
|---|---|---|
initialize |
Client → Kiro | 握手,声明能力 |
session/new |
Client → Kiro | 创建编码会话 |
session/prompt |
Client → Kiro | 发任务,等结果 |
session/update |
Kiro → Client | 流式推文本块(通知) |
session/request_permission |
Kiro → Client | 敏感操作审批 |
看着挺清晰对吧?但坑全藏在细节里。
模型切换:试了 8 种方式,全失败
我一开始的想法很简单:通过 ACP 协议指定模型。比如在 session/new 里加个 model 参数。
结果呢?
| 尝试方式 | 结果 |
|---|---|
session/new 加 model 参数 |
被忽略,还是默认模型 |
session/setModel 方法 |
Method not found |
session/configure 方法 |
Method not found |
_kiro.dev/commands/execute/model |
进程直接崩了(反序列化错误) |
kiro-cli acp --model X |
unexpected argument |
kiro-cli-chat acp --model X |
unexpected argument |
环境变量 KIRO_MODEL |
被忽略 |
~/.kiro/settings/cli.json 配置 |
被忽略 |
8 种方式,全军覆没。 差点掀桌子。
抓包分析了半天,结论是:ACP v1.25 协议设计上就不支持运行时切换模型。这不是 bug,是 feature(苦笑)。
但我发现了个替代方案:kiro-cli chat --model X --no-interactive。这个命令支持指定模型,但只能一次性运行,不支持多轮会话。
双通道架构:既要又要的工程方案
既然一条路走不通,那就两条路一起走。
HTTP 客户端(curl / Python / JS)
|
| HTTP REST (port 8642)
v
FastAPI Server (server.py)
|
-----+--------------
| |
| model=auto | model=指定模型
v v
ACP 通道 Chat 通道
acp_client.py subprocess
(常驻进程) (一次性进程)
多轮会话 单次调用
这就是双通道架构的核心思路:
| ACP 通道 | Chat 通道 | |
|---|---|---|
| 协议 | JSON-RPC 2.0 over stdio | subprocess + 文本解析 |
| 模型选择 | 不支持(固定 auto) | 支持 --model 参数 |
| 多轮会话 | 支持 | 不支持 |
| 性能 | 无冷启动,延迟低 | 每次冷启动约 3 秒 |
| 适用场景 | 多轮对话、上下文复用 | 指定模型的单次任务 |
设计原则:优先走 ACP 通道(性能好、支持多轮),只有在需要指定模型时才降级到 Chat 通道。
关键实现:Event + Pending Map 同步语义
ACP 底层是异步的 stdio 通信。你往 stdin 写一条请求,stdout 上可能先来几条通知,然后才是你的响应。怎么把异步变同步?
核心思路:每个请求分配唯一 id,用 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() # 唤醒等待线程
这套机制保证了:不管 Kiro 推了多少通知过来,你的请求一定能拿到对应的响应。
踩坑记录:session/update 才是文本载体
这个坑我踩了好几个小时。
session/prompt 发出去之后,最终响应长这样:
{"jsonrpc": "2.0", "id": 3, "result": {"stopReason": "end_turn"}}
里面没有文本内容! 一开始我以为是解析错了,反复检查了好几遍。
实际的文本是通过多个 session/update 通知逐块推过来的:
← {"method": "session/update", "content": {"type": "text", "text": "```python"}}
← {"method": "session/update", "content": {"type": "text", "text": "\ndef fibonacci(n):"}}
← ...(更多文本块)
← {"jsonrpc": "2.0", "id": 3, "result": {"stopReason": "end_turn"}}
所以正确的处理方式是:在 _session_updates[session_id] 列表里收集所有 chunk,收到 end_turn 后 "".join() 拼接。
这个在官方文档里没明确说。 得自己抓包才能发现。
权限请求:不回复就永远挂死
又一个坑。Kiro 在执行文件写入、终端命令这些敏感操作之前,会发一个权限请求:
{"jsonrpc": "2.0", "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 通道的坑在于:kiro-cli chat 的 stdout 是给人看的,不是给程序解析的。
原始输出里混着这些东西:
- ASCII 艺术 banner
- “Did you know?” 提示框
- “Model: claude-opus-4.6 | Plan: KIRO PRO+” 信息行
>前缀的实际回复内容 ← 这才是我要的- “▸ Credits: 0.09 • Time: 3s” 页脚
清洗流程:
- 正则去掉所有 ANSI 转义码
- 跳过 banner、提示框、Model/Plan 行
- 找到
>标记,提取后续内容 - 遇到
▸ Credits截止,丢弃页脚
说实话,这种文本解析挺脆弱的。Kiro CLI 版本一更新,输出格式可能就变了。但目前没有更好的办法。
对外 REST API:7 个端点
最终 FastAPI 服务提供这些接口:
| 接口 | 说明 |
|---|---|
POST /prompt |
快捷调用,支持指定模型 |
POST /sessions |
创建多轮会话(仅 auto 模型) |
POST /sessions/{id}/prompt |
向已有会话发送任务 |
GET /sessions |
列出所有活跃会话 |
DELETE /sessions/{id} |
删除指定会话 |
GET /models |
列出可用模型 |
GET / |
健康检查 |
调用示例:
# 快速提问
curl -X POST http://localhost:8642/prompt \
-H "Content-Type: application/json" \
-d '{"prompt": "写一个 Python 快速排序", "model": "auto"}'
# 创建会话 → 多轮对话
curl -X POST http://localhost:8642/sessions
curl -X POST http://localhost:8642/sessions/abc123/prompt \
-d '{"prompt": "帮我重构这段代码"}'
stdio 双向通信的死锁陷阱
这个问题比较隐蔽。Kiro CLI 进程的 stdout 和 stderr 都有输出。如果你只读 stdout 不管 stderr,stderr 的缓冲区满了之后,整个进程会阻塞——因为写不进去了。
所以 acp_client.py 里必须单独开一个线程来排空 stderr:
# 专门的 stderr 读取线程,防止缓冲区满导致死锁
def _drain_stderr(self):
for line in self._proc.stderr:
pass # 读掉就行,不需要处理
这个坑在 Windows 上尤其明显,因为 Windows 的管道缓冲区更小。Linux 上也不能忽视,高频输出时照样会阻塞。
已知限制
说完好处,也得说局限:
- 无鉴权:API 没做认证,只适合内网用,别暴露到公网
- 单 ACP 连接:高并发请求需要排队处理
- 会话不持久化:服务重启后会话丢失
- Chat 模式无上下文:每次都是新进程,不支持多轮
这些限制在个人使用和团队内网场景下可以接受。如果要上生产,还需要加鉴权、连接池、持久化这些工程化的东西。
总结
| 类别 | 结论 |
|---|---|
| 协议限制 | ACP v1.25 不支持运行时切换模型,协议设计限制 |
| 架构决策 | 双通道:多轮走 ACP,指定模型走 Chat |
| 关键发现 | session/update 通知才是文本载体 |
| 关键发现 | 权限请求必须回复,否则永久挂起 |
| 关键发现 | Chat 输出混杂 UI 元素,需专门清洗 |
| 关键发现 | stdio 双向通信必须读写线程分离 |
把 CLI 工具封装成 API,核心难点不在 HTTP 层,而在于理解协议的实际行为。官方文档没覆盖的部分,只能自己调试和抓包。
完整源码已在上文逐段展示,可直接复制使用。
本文基于亚马逊云科技官方博客内容整理,结合实际开发经验撰写。原文参见:将 Kiro CLI 封装为 REST API:双通道架构实践
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)