试了 8 种方式全失败后,我用双通道架构把 Kiro CLI 变成了 REST API

试了 8 种方式全失败后,我用双通道架构把 Kiro CLI 变成了 REST API

我最近在做一件"看起来不难但实际很折腾"的事:把 Kiro CLI 变成一个可以被其他服务调用的 REST API。

Kiro CLI 是亚马逊云科技推出的终端 AI 编码工具。代码生成、分析、重构,它都能干。但问题是------它只有 stdio 接口。你只能在终端里打字跟它聊天,没有 HTTP 端口,没有 SDK,没有任何可以被程序化调用的方式。

我想在自动化流水线里用它,怎么办?只能自己封装。

最终产物:两个文件,约 600 行 Python

先说结果,免得大家觉得是个大工程:

bash 复制代码
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 双向通信,这就是 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 种姿势,全军覆没

我想在 API 层支持动态切捡模型。按照正常思路,应该在协议里指定。

然而:

我的尝试 惨烈结果
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 协议设计上就不支持运行时切捡模型。这是协议层的限制,不是我用法不对。

但还有一条路:kiro-cli chat --model X --no-interactive。这个命令支持指定模型,代价是只能一次性运行,不能多轮对话。

破局:双通道架构

一条路搞不定两个需求,那就开两条路。

scss 复制代码
HTTP 客户端
    |
    | REST API (port 8642)
    v
FastAPI Server
    |
 ---+-------------
 |                |
 | model=auto     | model=指定
 v                v
ACP 通道         Chat 通道
(常驻进程)       (一次性子进程)
多轮会话         单次调用
JSON-RPC 2.0    文本解析
ACP 通道 Chat 通道
模型 不可选(auto) 可指定
多轮 支持 不支持
冷启动 ~3 秒
场景 日常多轮对话 指定模型的单次任务

设计原则很简单:能走 ACP 就走 ACP,只有指定模型时才降级到 Chat。

实现细节一:异步变同步

ACP 底层是异步 stdio。你往 stdin 写请求,stdout 上可能先推来一堆通知,然后才是你要的响应。

我用 Event + Pending Map 做了同步封装:

python 复制代码
# 发请求:注册等待
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 的最终响应:

json 复制代码
{"jsonrpc": "2.0", "id": 3, "result": {"stopReason": "end_turn"}}

没有文本! 我反复检查了好几遍,一度怀疑是不是哪里解析错了。

实际内容是通过 session/update 通知逐块推过来的:

swift 复制代码
← {"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 执行文件写入、命令执行前会发权限请求:

json 复制代码
{"id": 99, "method": "session/request_permission",
 "params": {"toolCall": {"title": "Creating app.py"}}}

这是个带 id 的 request。不回复的话,Kiro 永远等着,请求就挂死了。

Headless 模式下直接自动审批:

python 复制代码
def _handle_permission_request(self, msg_id, params):
    self._send_response(msg_id, {"optionId": "allow_always"})

实现细节四:Chat 输出清洗

Chat 通道的 stdout 是给人看的终端输出,混着各种 UI 元素:

  • ASCII 艺术 banner
  • "Did you know?" 提示框
  • Model/Plan 信息行
  • > 前缀的实际回复 ← 目标内容
  • ▸ Credits: 0.09 页脚

清洗步骤:

  1. 正则去 ANSI 转义码
  2. 跳过 banner 和提示框
  3. > 标记提取内容
  4. ▸ Credits 截止

这种文本解析确实脆弱,CLI 版本更新后格式可能变。但目前没有更好的办法。

实现细节五:别忘了排空 stderr

Kiro CLI 的 stderr 也有输出。如果只读 stdout 不管 stderr,缓冲区满了整个进程就阻塞。

必须开一个线程专门排空:

python 复制代码
def _drain_stderr(self):
    for line in self._proc.stderr:
        pass  # 读掉就行

这个问题在高频输出时特别容易触发。

REST API 端点一览

最终 FastAPI 服务在 8642 端口提供 7 个端点:

接口 方法 说明
/prompt POST 快捷调用,支持指定模型
/sessions POST 创建多轮会话
/sessions/{id}/prompt POST 会话内发送任务
/sessions GET 列出所有活跃会话
/sessions/{id} DELETE 删除指定会话
/models GET 列出可用模型
/ GET 健康检查

接口设计很标准的 RESTful 风格。/prompt 是最常用的,大部分场景直接用这个就行。需要多轮对话时才用 sessions 相关接口。

完整请求生命周期

把上面的细节串起来,一次完整的 ACP 请求是这样的:

  1. 客户端发 POST /prompt
  2. FastAPI 检测 model=auto,走 ACP 通道
  3. acp_client 调 session/new,拿到 session_id
  4. 构造 JSON-RPC 请求写入 stdin
  5. Kiro 处理任务,通过 session/update 推文本块
  6. 如果有敏感操作,Kiro 发权限请求,acp_client 自动审批
  7. Kiro 发最终响应 {"stopReason": "end_turn"}
  8. acp_client 拼接所有文本块
  9. FastAPI 返回 JSON 给客户端

Chat 通道更简单:

  1. 客户端发 POST /prompt,指定模型
  2. FastAPI 检测 model 不是 auto,走 Chat 通道
  3. 启动子进程:kiro-cli chat --model X --no-interactive
  4. 等进程结束,拿到 stdout
  5. 清洗输出,提取有效文本
  6. 返回结果

调用示例

笀单演示一下怎么用:

bash 复制代码
# 快速提问(走 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 种模型切换方式全失败,最后只能接受现实,用双通道绕过去。

完整源码已在上文逐段展示,可直接复制使用。


本文基于亚马逊云科技官方博客内容整理撰写。原文:将 Kiro CLI 封装为 REST API:双通道架构实践

相关推荐
zhojiew16 小时前
使用Redis Stream订阅HUATUO发布SSE内核可观测性事件并进行AI分析的数据管道实践
运维·hbase·aws
yyuuuzz3 天前
谷歌云使用的几个常见注意事项
运维·服务器·网络·安全·web安全·云计算·aws
zhojiew3 天前
在AWS中国区的EMR集群中实现基于向量语义搜索的HBase运维诊断系统
运维·hbase·aws
yyuuuzz3 天前
独立开发者线上服务运维的几点实践经验
运维·服务器·网络·云计算·aws
zhojiew3 天前
使用DBT(data build tool)集成AWS Athena完成数据处理的实践
云计算·aws
yyuuuzz5 天前
aws的核心概念与常见使用场景
运维·服务器·网络·云计算·aws
zhojiew5 天前
在AWS云上使用EC2 嵌套虚拟化实例部署Cube Sandbox的实践和问题
云计算·aws
yyuuuzz6 天前
国际云服务器的技术特点与使用经验
运维·服务器·网络·数据库·云计算·aws
我是小邵7 天前
从 Supabase 迁移到 AWS 的云架构演进实践
架构·云计算·aws
炸裂狸花猫7 天前
开源身份认证与访问管理平台 - Keycloak(三)公有云Console集成实践(AWS / 阿里云 / OCI)
阿里云·云原生·keycloak·aws·oci·sso