试了 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:双通道架构实践

相关推荐
亚马逊云开发者7 小时前
两个 AI Agent 互相调用是什么体验?Kiro + OpenClaw 双协议实战,架构评审从 2 天干到 15 分钟
aws
亚马逊云开发者20 小时前
更新个监控 Agent 要协调 200 个团队?Amazon ECS 托管守护进程终于把平台工程师从苦海里捞出来了
aws
亚马逊云开发者1 天前
5 个 Agent 协同处理金融业务,我用 Kiro + AgentCore 半天就部署上线了
aws
亚马逊云开发者1 天前
我把 Claude Code 的 Token 费砍了 70%,只用了 SageMaker + 一个路由 Hook
aws
圣殿骑士-Khtangc1 天前
Amazon CodeWhisperer 超详细使用教程:AWS 云原生 AI 编程助手上手指南
人工智能·ai编程·aws·编程助手·codewhisperer
翼龙云_cloud2 天前
亚马逊云代理商:如何在 AWS Lightsail 上一键部署 OpenClaw 私有化 AI 助手?
人工智能·云计算·aws·openclaw
Lim小刘3 天前
AWS IAM Identity Center 实战操作:从启用、用户、权限集到 SSO 登录
云计算·aws·云安全·sso
运维行者_4 天前
使用 Applications Manager 实现 AWS 云监控:保障业务应用高效运行
大数据·运维·服务器·网络·数据库·云计算·aws
admin and root4 天前
AWS S3 对象存储攻防&云安全之OSS存储桶漏洞
微信小程序·小程序·渗透测试·云计算·aws·src·攻防演练