kiro-gateway
Health Gecti
- License — License: AGPL-3.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 38 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Compliance-first AI gateway for Kiro: connect OpenAI, Anthropic, ACP, Claude Code, Cursor, OpenCode, and MCP through the official kiro-cli binary.
ACP-Compliant Kiro Gateway — use Kiro from the AI tools you already use
Use your Kiro subscription from Claude Code, OpenCode, Cursor, Kilo Code, and other OpenAI- or Anthropic-compatible clients. Kiro Gateway translates those HTTP APIs — plus native ACP — to the official kiro-cli binary over the Agent Client Protocol.
- Official integration only: no reverse-engineered Kiro endpoints, account pooling, or credential handling.
- One local gateway: OpenAI, Anthropic, and native ACP clients can share
http://localhost:8000. - MCP-aware: harness MCP servers can be discovered and forwarded to
kiro-cli; the gateway never runs them itself.
Quick start
Install uv and the
Kiro CLI, then run:
git clone https://github.com/ankitcharolia/kiro-gateway.git
cd kiro-gateway
uv sync
cp .env.example .env # set KIRO_GATEWAY_API_KEY to your own secret
kiro-cli login # authenticate once with the official CLI
uv run main.py # listen on http://localhost:8000
In another terminal, verify the gateway before configuring a client:
curl -fsS http://localhost:8000/health
Expected response: {"status":"ok", "mode":"acp-cli-bridge", "version":"…"}.
At a glance
| Works well | Deliberate boundaries |
|---|---|
| OpenAI Chat + Responses, Anthropic Messages, and native ACP | Embeddings return 501 because ACP exposes no embeddings model |
| Streaming and non-streaming responses with native reasoning/thinking shapes | Stateful Responses (previous_response_id) is rejected; store is a no-op |
| Images, PDF text extraction, and automatic harness MCP discovery | Client-declared function calling is not honored by kiro-cli; use MCP for external tools |
Works with: Claude Code · OpenCode · Cursor · Kilo Code · Cline · Continue · Hermes-agent · OpenClaw · Oh My Pi
⭐ If this project saves you time, star it to follow releases and compatibility updates.
💛 If you want to support ongoing maintenance, ☕ Buy Me a Coffee or 💸 PayPal.
Read in another language: Español · Português · 中文 · 日本語 · 한국어 · Русский · Bahasa Indonesia · documentation index
[!NOTE]
The root README is the canonical, most current guide. Translated documents are
helpful snapshots and may lag behind new endpoints or configuration options.
Compliance model
This gateway is designed to stay on Kiro's official, documented integration
surfaces:
- It communicates with Kiro only through the official
kiro-clibinary. - It never calls private HTTP endpoints, never pools accounts, and never
circumvents rate limits. - It never reads or stores credentials — all authentication lives inside
kiro-cli(kiro-cli login).
Full request path
OpenCode / Hermes-agent / Kilo Code / Claude Code / OpenClaw
(any OpenAI or Anthropic API client)
│
┌────────────────┴─────────────────┐
│ │
routes_openai_shim.py routes_anthropic_shim.py
POST /v1/chat/completions POST /v1/messages
GET /v1/models GET /v1/models
│ │
└────────────────┬─────────────────┘
│
┌───────────▼──────────┐
│ shim_service.py │
│ orchestration + tool │
│ call round-trips │
└───────────┬──────────┘
│
┌───────────▼──────────┐
│ acp_client.py │
│ JSON-RPC 2.0 over │
│ stdio │
└───────────┬──────────┘
│
┌───────────▼──────────┐
│ kiro-cli │ ← only official binary
│ (official, authed) │
└───────────┬──────────┘
│
┌───────────▼──────────┐
│ Kiro Backend │
└──────────────────────┘
Single-account enforcement — the gateway validates at startup that only one kiro-cli session is active at any time (
kiro.compliance.validate_single_account_compliance). Attempting to spin up parallel accounts raises a hardComplianceError.
Modes
1. ACP (preferred)
For editors that speak ACP natively.
| Endpoint | Description |
|---|---|
POST /acp/chat |
Non-streaming ACP conversation |
POST /acp/chat/stream |
SSE streaming ACP conversation |
2. OpenAI shim
For tools that only understand the OpenAI API (Cursor, Cline, Continue, OpenCode, Hermes-agent, OpenClaw, Oh My Pi, …).
| Endpoint | Description |
|---|---|
GET /v1/models |
List available models (live catalogue from kiro-cli, with fallback) |
GET /v1/models/{model} |
Retrieve a single model object (probed by some harnesses, e.g. hermes-agent) |
POST /v1/chat/completions |
Streaming and non-streaming completions |
POST /v1/responses |
OpenAI Responses API (streaming and non-streaming) |
POST /v1/embeddings |
Returns 501 Not Implemented — kiro-cli/ACP provides no embeddings model |
3. Anthropic shim
For tools that only understand the Anthropic API (Claude Code, Kilo Code, OpenClaw, Oh My Pi, …).
| Endpoint | Description |
|---|---|
GET /v1/models |
List available models (live catalogue from kiro-cli, with fallback) |
GET /v1/models/{model} |
Retrieve a single model object |
POST /v1/messages |
Streaming and non-streaming messages |
POST /v1/messages/count_tokens |
Estimate input tokens for a request (local tokenizer estimate) |
Installation details
Prerequisites
| Requirement | Notes |
|---|---|
| Kiro CLI | Install from kiro.dev and run kiro-cli login |
| Python 3.14+ | Required |
Clone and run (bare metal)
# 1. Clone the repository
git clone https://github.com/ankitcharolia/kiro-gateway.git
cd kiro-gateway
# 2. Install dependencies (uv creates .venv automatically)
# Install uv first: https://docs.astral.sh/uv/getting-started/installation/
uv sync
# 3. Configure environment
cp .env.example .env
# Open .env and set at minimum:
# KIRO_GATEWAY_API_KEY=<your-chosen-secret-key>
# KIRO_CLI_PATH=kiro-cli # full path if not on $PATH
# 4. Authenticate with Kiro (once)
kiro-cli login
# 5. Start the gateway
uv run main.py
# Gateway is now listening on http://localhost:8000
Run as a service
Start automatically on login and restart on crash — no terminal required.
bash scripts/install-service.sh # install & start
bash scripts/install-service.sh --uninstall # remove
The script auto-detects your repo root and venv, writes the platform-native
service file (systemd on Linux, launchd on macOS), and starts the gateway.
Template files with comments are in scripts/kiro-gateway.service (Linux) andscripts/kiro-gateway.plist (macOS) if you prefer to configure manually.
Configuration
All settings are read from environment variables (or a .env file).
# ── Required ──────────────────────────────────────────────────────────
KIRO_GATEWAY_API_KEY=change-me # Secret key clients must send as Bearer / x-api-key
# ── CLI path ──────────────────────────────────────────────────────────
KIRO_CLI_PATH=kiro-cli # Override if kiro-cli is not on $PATH
# ── Models ────────────────────────────────────────────────────────────
# How to handle a requested model that isn't in kiro-cli's live catalogue.
# warn (default) = log a warning and fall back to the session default;
# strict = reject with a 404 (native error shape);
# off = forward silently (legacy). Validation is skipped until the
# live catalogue is discovered (first session).
MODEL_VALIDATION=warn
# Optional model-id aliases so a harness that hardcodes a foreign id can be
# mapped to a real kiro-cli model (resolved before validation). Comma-separated
# alias=target pairs.
MODEL_ALIASES=
# Enforce max_tokens by capping the output stream (kiro-cli doesn't honor it).
# Default false (no cap). true = stop generation at the limit (finish_reason
# length). stop sequences are always enforced when a client sends them.
ENFORCE_MAX_TOKENS=false
# ── Tool execution ────────────────────────────────────────────────────
# kiro-cli runs its own built-in tools (file edits, command execution) and
# asks the gateway for permission first. true = auto-approve each request,
# false = reject (read/answer-only posture).
ACP_TRUST_TOOLS=true
# How the shims present kiro-cli's own built-in tool activity. Default false =
# inline, non-executable reasoning text (interleaved with thinking, like
# kiro-cli; needs ACP_SURFACE_THINKING=true) — works with every harness. true =
# executable tool_calls/tool_use (only for ACP-aware UIs that just display
# activity). The native /acp/chat route always surfaces a structured tool call.
ACP_SURFACE_TOOL_CALLS=false
# Surface kiro-cli's reasoning/"thinking" in each API's native reasoning shape
# (OpenAI reasoning_content / Responses reasoning items; Anthropic thinking
# blocks). Default true — reasoning is additive and never changes the final
# answer. Set false to emit only the final answer.
ACP_SURFACE_THINKING=true
ACP_WORKSPACE_DIR= # Fallback session cwd. By default the harness
# cwd is auto-detected per request (X-Kiro-Workspace
# header, filesystem_roots, or the prompt's <env>
# "Working directory:" line); this is only used when
# none is present (else the process cwd).
ACP_TIMEOUT=120 # Seconds to await a JSON-RPC response
ACP_STDIO_MAX_BYTES=16777216 # Max bytes per ACP stdout line (16 MiB) — raise
# for very large tool outputs in long agent turns
# ── ACP session mode & spawn args ─────────────────────────────────────
# Agent persona selected per session via session/set_mode (kiro_default,
# code, kiro_planner, kiro_guide). Empty = kiro-cli's default mode.
KIRO_ACP_MODE=
# Flags passed to `kiro-cli acp` at launch (all optional). The engine is
# pinned explicitly (default v2) so a future change to kiro-cli's default
# engine can't silently alter behaviour. v3 needs host-mediated auth the
# gateway does not implement (generation fails) — keep v2.
KIRO_ACP_ENGINE=v2 # v1 | v2 | v3
KIRO_ACP_AGENT= # --agent: (custom) agent config for the first session
KIRO_ACP_MODEL= # --model: initial session model (per-request model still wins)
KIRO_ACP_EFFORT= # --effort: low | medium | high | xhigh | max
KIRO_ACP_EXTRA_ARGS= # extra raw args appended verbatim (shell-quoted)
# ── MCP servers (external tools kiro-cli runs itself) ─────────────────
# MCP is the only external-tool channel kiro-cli honors over ACP (the gateway
# never runs the tools, so compliance is preserved). USUALLY YOU DON'T NEED
# THESE — there are two zero-config paths already:
# 1. Your HARNESS's own MCP servers (Claude Code, OpenCode, Oh My Pi, Cursor,
# Kilo Code) are AUTO-DISCOVERED from its config files and forwarded on
# every session/new. On by default; see MCP_DISCOVERY below.
# 2. Servers added with `kiro-cli mcp add` (global ~/.kiro/settings/mcp.json
# or a per-agent config) are auto-loaded by kiro-cli itself.
# The vars below are an OPTIONAL SUPPLEMENT: a list the gateway injects on every
# session/new, ADDED on top of discovered + kiro-cli's own config.
# Provide inline JSON via KIRO_MCP_SERVERS OR a file path via KIRO_MCP_CONFIG;
# each accepts an ACP mcpServers array or the mcp.json object form. An HTTP
# entry MUST carry type:"http" and headers as an ARRAY — the gateway normalises
# the common object form ({"Authorization":"Bearer …"}) to [{"name","value"}]
# automatically (a wrong shape makes kiro-cli's session/new silently block).
# KIRO_MCP_SERVERS=[{"type":"http","name":"docs","url":"http://127.0.0.1:9000/mcp","headers":[]}]
KIRO_MCP_SERVERS=
KIRO_MCP_CONFIG=
# Seconds cap for session/new WHEN MCP servers are registered, so a
# malformed/unreachable server fails fast instead of stalling every request for
# ACP_TIMEOUT (a fresh session is opened per request). Default 30.
MCP_INIT_TIMEOUT=30
# ── Server ────────────────────────────────────────────────────────────
SERVER_HOST=0.0.0.0
SERVER_PORT=8000
# ── Compliance ────────────────────────────────────────────────────────
COMPLIANCE_MODE=true # Enforces single-account; set false only for development
Client setup
OpenAI-compatible clients
(Cursor, Cline, Continue, OpenCode, Hermes-agent, OpenClaw, Oh My Pi, …)
| Setting | Value |
|---|---|
| Base URL | http://localhost:8000/v1 |
| API Key | value of KIRO_GATEWAY_API_KEY |
| Model | claude-sonnet-4.6 (or any Kiro-supported model: auto, claude-opus-4.8, …) |
Anthropic-compatible clients
(Claude Code, Kilo Code, OpenClaw, Oh My Pi, …)
| Setting | Value |
|---|---|
| Base URL | http://localhost:8000 |
| API Key header | x-api-key: <KIRO_GATEWAY_API_KEY> |
| Model | claude-sonnet-4.6 |
How to integrate an AI harness
Ready-to-use config examples for common harnesses live inexamples/clients/. Each file is named<harness>-<api> so you can pick the one matching your tool and the API it
speaks, copy it to the harness's config location, and setKIRO_GATEWAY_API_KEY as the key.
| Harness | API | Example config | Save to |
|---|---|---|---|
| OpenCode | OpenAI | opencode-openai.json |
~/.config/opencode/opencode.json |
| Kilo Code | OpenAI | kilocode-openai.json |
~/.config/kilo/kilo.jsonc |
| Kilo Code | Anthropic | kilocode-anthropic.json |
~/.config/kilo/kilo.jsonc |
| Claude Code | Anthropic | claude-code-anthropic.json |
~/.claude/settings.json |
| Hermes-agent | OpenAI | hermes-agent-openai.yaml |
~/.hermes/config.yaml |
| OpenClaw | OpenAI | openclaw-openai.json |
OpenClaw config (merge the providers block) |
| OpenClaw | Anthropic | openclaw-anthropic.json |
OpenClaw config (merge the providers block) |
| Oh My Pi | OpenAI | oh-my-pi-openai.yaml |
~/.omp/agent/models.yml |
| Oh My Pi | Anthropic | oh-my-pi-anthropic.yaml |
~/.omp/agent/models.yml |
Each example carries its destination path as a comment on the first line.
Replace the placeholder change-me (or ${KIRO_GATEWAY_API_KEY}) with your
gateway key before saving. (The Claude Code example's settings.json is strict
JSON — remove the // comment lines before saving it.)
Native ACP clients
Point your ACP client at:
http://localhost:8000/acp/chat # non-streaming
http://localhost:8000/acp/chat/stream # SSE streaming
Streaming event map
| ACP event | OpenAI SSE | Anthropic SSE |
|---|---|---|
text |
delta.content chunk |
content_block_delta[text_delta] |
tool_call |
folded into reasoning_content by default; delta.tool_calls chunk when ACP_SURFACE_TOOL_CALLS=true |
folded into a thinking block by default; content_block_start[tool_use] + input_json_delta when surfacing |
thinking |
delta.reasoning_content chunk (Responses: response.reasoning_summary_text.delta) |
content_block_start[thinking] + thinking_delta |
plan (task list) |
folded into reasoning_content (Responses reasoning item) |
folded into a thinking block |
done |
[DONE] + finish_reason |
message_delta + message_stop |
error |
error chunk + [DONE] |
error event |
Your harness's MCP servers work automatically
If you registered MCP servers in Claude Code, OpenCode, Oh My Pi, Cursor or
Kilo Code, they work through the gateway with no configuration. This is on by
default — there is nothing to set.
Why this needed fixing
A harness never sends its MCP configuration over the wire: the OpenAI and
Anthropic APIs have no MCP channel. Verified against live captures, Claude Code'sPOST /v1/messages body carries model, messages, system, tools,thinking — and no MCP field or header; OpenCode flattens its MCP tool into
an ordinary function entry ({"type": "function", "function": {"name": "myserver_mytool", …}}). Those client-declared tools are ignored by kiro-cli
(issue #31), so an
OpenCode session with an MCP server configured used to answer "that tool isn't
available" through the gateway
(issue #75).
How it works
kiro-cli does honor MCP servers registered on session/new — including
stdio servers (verified against a live kiro-cli 2.17.0 probe: a{"name","command","args","env"} entry produced mcp/server_initialized, and
both tools/list and tools/call reached the server process). That matters
because most harness MCP servers are stdio (npx -y some-mcp@latest).
So the gateway reads the harness's own MCP config and registers the same
servers on every session. kiro-cli then connects to them and executes the
tools — the gateway never runs a tool, so the compliance model is unchanged, and
no server-side state is introduced.
Harness config (~/.claude/settings.json, opencode.json, .mcp.json, …)
│ read by the gateway, anchored on the request's workspace
▼
session/new mcpServers: [ …the harness's own servers… ]
▼
kiro-cli connects / spawns them and runs the tools itself
Discovered locations (workspace-level wins over user-level; duplicates by name
are removed):
| Harness | Config read |
|---|---|
| Claude Code | ~/.claude/settings.json, ~/.claude.json (per-project), <ws>/.mcp.json |
| OpenCode | ~/.config/opencode/opencode.json[c], <ws>/opencode.json[c] (mcp block) |
| Oh My Pi | ~/.omp/agent/mcp.json, <ws>/.omp/mcp.json, <ws>/mcp.json |
| VS Code / Cursor / Kilo Code | <ws>/.vscode/mcp.json, <ws>/.cursor/mcp.json, ~/.config/kilo/mcp.json |
Both the stdio form (command/args/env) and the remote form
(type/url/headers) are understood, enabled: false / disabled: true
entries are skipped, ${VAR} / ${env:VAR} placeholders are expanded from the
gateway's environment, and // comments plus trailing commas are tolerated. A
malformed harness config is logged and skipped — it never fails your request.
The workspace is the one already resolved per request (the X-Kiro-Workspace
header, filesystem_roots, or the <env> Working directory: line), so each
harness gets its own project's servers.
Narrowing or disabling it
MCP_DISCOVERY is optional and only needed to restrict the default:
| Value | Behaviour |
|---|---|
unset / all |
Default. User-level and workspace-level configs. |
user |
User-level configs only. |
off |
No discovery — only KIRO_MCP_SERVERS and kiro-cli's own config. |
[!IMPORTANT]
Discovery makes kiro-cli launch or connect to whatever the config names —
the same servers your harness already runs for you. The one case that widens
trust is a workspace-level file (<ws>/.mcp.json): it is controlled by
whoever wrote the repository. If your harnesses open untrusted repositories,
setMCP_DISCOVERY=user.
Precedence when several sources supply servers (deduplicated by name):
- Per-request —
X-Kiro-MCP-Serversheader or anmcp_serversbody field. - Discovered harness servers.
- Operator-configured
KIRO_MCP_SERVERS/KIRO_MCP_CONFIG.
Servers added with kiro-cli mcp add are loaded by kiro-cli itself on top of
all of these.
Error handling
ACP/upstream failures are classified into the correct HTTP status code and
returned in each API's native error shape (instead of a generic 502 with
a bare {"detail": ...} body), so harness retry/back-off logic behaves
correctly. The same classification applies to both shims and both
streaming and non-streaming paths.
| Upstream condition | HTTP status | OpenAI error type |
Anthropic error type |
|---|---|---|---|
Rate limit / throttle / quota / 429 |
429 |
rate_limit_error |
rate_limit_error |
| Overloaded / unavailable / capacity | 503 |
server_error |
overloaded_error |
| Timeout / deadline exceeded | 504 |
server_error |
api_error |
| Any other failure (default) | 502 |
server_error |
api_error |
- Non-streaming. The response status code is the mapped code and the body
is the native envelope — OpenAI{"error": {"message", "type", "code", "param"}}; Anthropic{"type": "error", "error": {"type", "message"}}. When
the upstream message carries a retry hint (e.g. "retry after 30"), aRetry-Afterheader is added (most relevant for429). - Streaming. The SSE stream has already begun (HTTP
200), so the mapped
errortypeis carried in the terminal error event: an OpenAI error chunk
(followed by[DONE]), a Responsesresponse.failedevent, or an Anthropicerrorevent.
Classification is message-based (kiro-cli surfaces upstream conditions as
text), so it works identically whether the failure arrives as a JSON-RPC error
on a non-streaming completion or as a streaming error event.
System & developer roles
Instruction provenance is preserved rather than flattened into anonymous user
text. ACP has no dedicated system channel, so a fresh-session prompt is
serialised into a single text block — but each message keeps its role label
(System: / Developer: / User: / Assistant:) and its order, and multiple
system messages are kept distinct rather than merged.
| Client input | Carried as | Rendered prompt label |
|---|---|---|
OpenAI system message |
system role |
System: |
OpenAI developer message |
developer role |
Developer: |
OpenAI assistant tool_calls |
assistant role (with [tool_use id=… name=…] markers) |
Assistant: |
OpenAI tool message |
user role (with [tool_result id=…] marker) |
User: |
OpenAI Responses instructions |
system role |
System: |
Anthropic system field (string or block list) |
system role |
System: |
Anthropic tool_use block |
assistant role (with [tool_use id=… name=…] marker) |
Assistant: |
Anthropic tool_result block |
user role (with [tool_result id=…] marker) |
User: |
kiro-cli treats the whole serialised prompt as one turn; these labels make
the system/developer instructions legible to the agent without inventing a
system channel the protocol does not expose.
Multi-turn & tool history fidelity
ACP's session/prompt carries a single, role-less list of content blocks
(the protocol's content block has no per-message role field), and ACP keeps
multi-turn state server-side across prompts within one session. The gateway is
stateless — a fresh session per request — so it must serialise the whole
conversation into one prompt. The faithful maximum ACP allows is therefore a
single text block whose turns are delimited by stable Role: labels, in order,
separated by blank lines:
- Turn boundaries are explicit and ordered (
System:/Developer:/User:/Assistant:); multiple system messages stay distinct. - Tool turns are preserved, not dropped. A prior assistant tool call —
OpenAItool_callsor Anthropictool_use— is rendered as[tool_use id=… name=…]followed by its arguments, and a tool result
(OpenAItoolrole or Anthropictool_result) as[tool_result id=…]with
its content. The two shims use the same markers, so the transcript
kiro-cli sees is consistent across both APIs. - Limitation (documented). Because ACP attaches no role to a content block,
the gateway does not — and cannot — send structured, role-tagged turns;
splitting the transcript into multiple blocks would add no structural
fidelity (kiro-cli concatenates them) while risking ambiguous boundaries. A
single labelled, blank-line-delimited block is the stable, faithful
representation. Client-side function calling itself is still not honored by
kiro-cli over ACP (see Tool-call round-trips and
issue #31); this section is purely about how prior tool turns are carried
in the prompt history.
Reasoning / thinking
kiro-cli emits reasoning ("thinking") while it works. The gateway surfaces it
in each API's native reasoning shape so reasoning-aware harnesses (Kilo
Code, Oh My Pi, Claude Code, …) can display it. Reasoning is additive — the
final answer text is never changed — and is gated by ACP_SURFACE_THINKING
(default true; set false to emit only the final answer).
| API / mode | Reasoning surface |
|---|---|
OpenAI /v1/chat/completions (stream) |
choices[].delta.reasoning_content chunks (DeepSeek/OpenAI-compatible convention) |
OpenAI /v1/chat/completions (non-stream) |
choices[].message.reasoning_content |
OpenAI /v1/responses (stream) |
a reasoning output item + response.reasoning_summary_part.added / response.reasoning_summary_text.delta / …done events |
OpenAI /v1/responses (non-stream) |
a {"type": "reasoning", "summary": [{"type": "summary_text", …}]} item in output |
Anthropic /v1/messages (stream) |
content_block_start[thinking] + content_block_delta[thinking_delta] + content_block_stop (before the text block) |
Anthropic /v1/messages (non-stream) |
a {"type": "thinking", "thinking": …} content block (before the text block) |
- The Anthropic
thinkingblocks are emitted without a cryptographicsignature(kiro-cli does not provide one over ACP), so they are suitable
for display; they are not intended to be signed/replayed back on a
follow-up tool turn. - The native
/acp/chatroute always surfaces reasoning as anacp_thinking
event regardless of this flag.
[!IMPORTANT]
Not seeing any reasoning? It's model-dependent. kiro-cli only emits
thinking for some models (e.g.claude-opus-4.8does;claude-sonnet-4.6
doesn't) and may skip it on trivial prompts — none of which affects the final
answer.Thinking effort is controllable over ACP (verified against a live
kiro-cli 2.12.0 probe): as a launch flag —kiro-cli acp --effort <low|medium|high|xhigh|max>, exposed by the gateway asKIRO_ACP_EFFORT
(see Configuration) — and per session via the_kiro.dev/commands/execute/effortslash-command extension (kiro-cli
advertises/effortin its_kiro.dev/commands/availablelist). What the
gateway does not do yet is translate a per-requestreasoning_effort
/thinking.budget_tokensinto that control: those client params are
currently only forwarded under_meta(which kiro-cli ignores), so a
per-request effort has no effect today. Wiring per-request effort to the/effortextension is tracked in
issue #59; until
then, set a session-wide default viaKIRO_ACP_EFFORT.
Task list / plan
kiro-cli expresses a multi-step task list through its built-in todo tool
(it has no standard ACP plan update). The gateway normalizes that into aplan event and surfaces it under the same ACP_SURFACE_THINKING flag:
- Native
/acp/chat/stream→ a structuredacp_planevent
({entries: [{content, status}], description}). - OpenAI / Anthropic shims → the task list is rendered as a checklist and
folded into the reasoning channel (reasoning_content/ Responses reasoning
item / Anthropicthinkingblock), since those APIs have no task-list field.
kiro-cli emits the list when it's created (entries
pending); it does not
re-emit a full snapshot as items complete, so surfaced statuses reflect
creation time. The final answer text is unaffected.
Generation parameters
Sampling parameters are accepted on both shims, validated, and forwarded tokiro-cli on every turn (streaming and non-streaming) under the ACPsession/prompt request's reserved _meta.generationConfig extension (only the
keys the caller set are included).
| Client field | Forwarded as | OpenAI | Anthropic |
|---|---|---|---|
temperature |
temperature |
✓ | ✓ |
max_tokens / max_output_tokens |
maxTokens |
✓ | ✓ |
top_p |
topP |
✓ | ✓ |
top_k |
topK |
— | ✓ |
stop / stop_sequences |
stopSequences |
✓ | ✓ |
[!IMPORTANT]
kiro-cli itself honors none of these over ACP, so the gateway enforces the
two it can apply to the output stream:
stopsequences are enforced by the gateway — generation is cut at the
first stop string (finish_reason=stop) and kiro-cli is cancelled. Always
on when a client suppliesstop.max_tokensis enforced whenENFORCE_MAX_TOKENS=true(default off) —
the output is capped at the limit (finish_reason=length). Off by default
to preserve the historical no-cap behaviour.temperature/top_p/top_kremain inert — they steer sampling
during generation, which only kiro-cli can do; they are forwarded under_metafor forward-compatibility. Other OpenAI params (seed,n,frequency_penalty,presence_penalty,logit_bias) are likewise ignored.
The one model control kiro-cli honors natively is the model
(session/set_model).
Structured outputs (no-op, but accepted & forwarded)
JSON mode / structured outputs (response_format), strict tool schemas
(strict) and tool-selection (tool_choice) are accepted on both shims and
forwarded to kiro-cli under the schema-safe _meta extension, but they are
not honored over ACP today — a request that sets them validates and
succeeds, returning free-form text (the model isn't constrained to the schema).
They are forwarded (not dropped) so they take effect automatically if a futurekiro-cli honors them. Mapping:
| Client field | API | Forwarded as |
|---|---|---|
response_format |
OpenAI chat (json_object / json_schema) |
_meta.structuredOutput.responseFormat |
text / response_format |
OpenAI Responses (text.format) |
_meta.structuredOutput.responseFormat |
tool_choice |
OpenAI + Anthropic | _meta.structuredOutput.toolChoice |
parallel_tool_calls |
OpenAI | accepted (no-op) |
strict (per tool function) |
OpenAI | strict on the tool in _meta.tools |
[!NOTE]
The Anthropic Messages API has noresponse_formatfield (structured
output is expressed through tools), so onlytool_choiceis accepted there.
Strict schemas are accepted either way (the Oh My PidisableStrictTools
workaround is no longer needed), though strict enforcement is still inert. See
issue #35.
Tool-call round-trips
kiro-cli over ACP is an autonomous agent, not a raw function-calling
model. Two distinct cases:
- kiro-cli's own built-in tools (code, file read/edit, shell, AWS, web
search) — fully supported. kiro-cli decides to run them, asks the gateway
for permission, executes them in the session directory, and continues the
turn. Harnesses drive these and get the final answer. - Client-declared tools (the harness's own functions) — not honored by
kiro-cli today. Definitions onsession/prompt(top-leveltoolsor_meta.tools) are accepted but ignored; ACP's only external-tool channel is
MCP servers registered atsession/new(executed by the MCP server, not
the harness). This is why your harness's MCP tools now work anyway: the
gateway discovers the harness's own MCP config and registers those same
servers on every session — see
Your harness's MCP servers.
Operator-configured servers (KIRO_MCP_SERVERS/KIRO_MCP_CONFIG) are
forwarded too, and client tool defs are still forwarded under_meta.tools
for forward-compatibility — but OpenAI/Anthropic-style client-side function
calling does not round-trip today. See
issue #31.
By default (ACP_SURFACE_TOOL_CALLS=false) the shims surface kiro-cli's
built-in tool activity as inline, non-executable reasoning — each tool run
(⚙ Reading config.py, ⚙ Running: …) is folded into the reasoning channel,
interleaved with thinking and before the answer (rides ACP_SURFACE_THINKING;
live interleaving needs streaming). The harness gets a normal completion
(finish_reason=stop/end_turn), never a tool call it can't run — so this
works with every harness. File edits render as a fenced ```diff block;
shell commands render the command + a fenced output block; file reads show only
the label.
When ACP_SURFACE_TOOL_CALLS=true (opt-in, for ACP-aware UIs), the activity is
instead emitted as executable-shaped tool_calls/tool_use events. Because
kiro-cli names each built-in call with a prose title ("Running: echo …",
"Reading README.md:1-5"), the gateway maps it onto a recognisable tool name
by kind — preferring the caller's own declared tool (e.g. kiro'sexecute→Bash, read→Read) and falling back to a canonical name — and
strips kiro's internal __tool_use_purpose arg. This is display/naming
normalisation, not a client-side function-calling round-trip (kiro-cli already
ran the tool inside the turn). The native /acp/chat route always surfaces a
structured tool call. Either way, tool
execution happens entirely inside kiro-cli within one turn — callers never
execute tools themselves.
Tool execution & permissions
kiro-cli ships its own built-in tools (file reads/edits, command
execution, search, …) and runs them itself inside the session working
directory. The gateway advertises no client-side filesystem or terminal
capabilities, so it never executes tools on the agent's behalf — it only
answers the permission requests the agent sends back:
| Agent request | Gateway behaviour |
|---|---|
session/request_permission |
Auto-approves a single invocation (allow_once) when ACP_TRUST_TOOLS=true; rejects (reject_once) when false. |
ACP_TRUST_TOOLS=true # auto-approve built-in tool runs (file edits, commands)
ACP_TRUST_TOOLS=false # answer-only: every tool permission request is denied
ACP_WORKSPACE_DIR=/path # working directory kiro-cli operates in (default: process cwd)
Surfacing vs. execution.
ACP_TRUST_TOOLScontrols whether kiro-cli is
allowed to run its built-in tools;ACP_SURFACE_TOOL_CALLScontrols how
that activity is shown to the OpenAI/Anthropic shims. KeepACP_TRUST_TOOLS=trueso the agent can use its full toolset. WithACP_SURFACE_TOOL_CALLS=false(default) the activity is shown as inline
reasoning text (interleaved, non-executable) so every harness works; set ittrueonly for ACP-aware UIs that want executabletool_calls/tool_use.
Security: with
ACP_TRUST_TOOLS=truethe agent can write files and run
commands inACP_WORKSPACE_DIRwithout human confirmation. Usefalsefor a
read/answer-only deployment, and scopeACP_WORKSPACE_DIRto a project
directory.
Support
If this project is useful, please consider supporting its continued development — ☕ Buy Me a Coffee or 💸 PayPal.
Compliance & licensing
The gateway talks to Kiro only through the official kiro-cli binary — it never
calls private endpoints, pools accounts, or handles credentials. This reflects
the project's design intent and the maintainer's reading of Kiro's official
integration paths, not legal advice. The Kiro CLI is licensed as "AWS Content"
under the AWS Customer Agreement and the
AWS IP License (see the
official license), so your use is governed by those
terms. If you plan to redistribute the Kiro CLI, review
the redistribution terms first. See COMPLIANCE.md for details.
License
AGPL-3.0 — preserving upstream license requirements.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi