API / kiro-gateway /docs /en /ARCHITECTURE.md
sshinmen's picture
Clean deploy to HF Space
bf9e111
|
Raw
History Blame Contribute Delete
38.8 kB
# Architectural Overview: Kiro Gateway
## 1. System Purpose and Goals
The project is a high-level proxy gateway implementing the **"Adapter"** structural design pattern.
The main goal of the system is to provide transparent compatibility between multiple heterogeneous interfaces:
### Supported API Formats
| API | Endpoints | Status |
|-----|-----------|--------|
| **OpenAI** | `/v1/models`, `/v1/chat/completions` | βœ… Supported |
| **Anthropic** | `/v1/messages` | βœ… Supported |
### Architectural Model
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Clients β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ OpenAI SDK/Tools β”‚ β”‚ Anthropic SDK/Tools β”‚ β”‚
β”‚ β”‚ (Cursor, Cline, β”‚ β”‚ (Claude Code, β”‚ β”‚
β”‚ β”‚ Continue, etc.) β”‚ β”‚ Anthropic SDK) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Kiro Gateway β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ OpenAI Adapter β”‚ β”‚ Anthropic Adapter β”‚ β”‚
β”‚ β”‚ /v1/chat/... β”‚ β”‚ /v1/messages β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Core Layer β”‚ β”‚
β”‚ β”‚ (Shared conversion logic) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Kiro API β”‚
β”‚ (AWS CodeWhisperer Backend) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
The system acts as a "translator", allowing the use of any tools, libraries, and IDE plugins developed for OpenAI and Anthropic ecosystems with Claude models through the Kiro API.
**Both APIs work simultaneously** on the same server without any configuration switching.
## 2. Project Structure
The project is organized as a modular Python package `kiro/`:
```
kiro-gateway/
β”œβ”€β”€ main.py # Entry point, FastAPI application creation
β”œβ”€β”€ requirements.txt # Python dependencies
β”œβ”€β”€ .env.example # Environment configuration example
β”‚
β”œβ”€β”€ kiro/ # Main package
β”‚ β”œβ”€β”€ __init__.py # Package exports, version
β”‚ β”‚
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”‚ # SHARED LAYER - Reused by all APIs
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”œβ”€β”€ config.py # Configuration and constants
β”‚ β”œβ”€β”€ auth.py # KiroAuthManager - token management
β”‚ β”œβ”€β”€ cache.py # ModelInfoCache - model cache
β”‚ β”œβ”€β”€ http_client.py # HTTP client with retry logic
β”‚ β”œβ”€β”€ parsers.py # AWS SSE stream parsers
β”‚ β”œβ”€β”€ utils.py # Helper utilities
β”‚ β”œβ”€β”€ tokenizer.py # Token counting (tiktoken)
β”‚ β”œβ”€β”€ debug_logger.py # Debug request logging
β”‚ β”œβ”€β”€ exceptions.py # Exception handlers
β”‚ β”œβ”€β”€ thinking_parser.py # Thinking blocks parser
β”‚ β”‚
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”‚ # CORE LAYER - Shared core for all APIs
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”œβ”€β”€ converters_core.py # Shared Kiro payload building logic
β”‚ β”œβ”€β”€ streaming_core.py # Shared Kiro stream parsing logic
β”‚ β”‚
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”‚ # OPENAI API LAYER
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”œβ”€β”€ models_openai.py # Pydantic models for OpenAI API
β”‚ β”œβ”€β”€ converters_openai.py # OpenAI β†’ Kiro adapter
β”‚ β”œβ”€β”€ routes_openai.py # FastAPI routes for OpenAI
β”‚ β”œβ”€β”€ streaming_openai.py # Kiro β†’ OpenAI SSE formatter
β”‚ β”‚
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”‚ # ANTHROPIC API LAYER
β”‚ β”‚ # ═══════════════════════════════════════════════════════
β”‚ β”œβ”€β”€ models_anthropic.py # Pydantic models for Anthropic API
β”‚ β”œβ”€β”€ converters_anthropic.py # Anthropic β†’ Kiro adapter
β”‚ β”œβ”€β”€ routes_anthropic.py # FastAPI routes for Anthropic
β”‚ └── streaming_anthropic.py # Kiro β†’ Anthropic SSE formatter
β”‚
β”œβ”€β”€ tests/ # Tests
β”‚ β”œβ”€β”€ conftest.py # Pytest fixtures
β”‚ β”œβ”€β”€ unit/ # Unit tests
β”‚ └── integration/ # Integration tests
β”‚
β”œβ”€β”€ docs/ # Documentation
β”‚ β”œβ”€β”€ ru/ # Russian version
β”‚ └── en/ # English version
β”‚
└── debug_logs/ # Debug logs (generated when DEBUG_LAST_REQUEST=true)
```
### Organization Principle: Shared Core + Thin Adapters
The architecture is built on the principle of **maximum code reuse**:
| Layer | Purpose | Files |
|-------|---------|-------|
| **Shared Layer** | Infrastructure independent of API format | `auth.py`, `http_client.py`, `cache.py`, `parsers.py`, `tokenizer.py` |
| **Core Layer** | Shared business logic for conversion | `converters_core.py`, `streaming_core.py` |
| **API Layer** | Thin adapters for specific formats | `*_openai.py`, `*_anthropic.py` |
## 3. Architectural Topology and Components
The system is built on the asynchronous `FastAPI` framework and uses an event-driven lifecycle management model (`Lifespan Events`).
### 3.1. Entry Point (`main.py`)
The `main.py` file is responsible for:
1. **Logging configuration** β€” Loguru setup with colored output
2. **Configuration validation** β€” `validate_configuration()` function checks:
- Presence of `.env` file
- Presence of credentials (REFRESH_TOKEN or KIRO_CREDS_FILE)
3. **Lifespan Manager** β€” creation and initialization of:
- `KiroAuthManager` for token management
- `ModelInfoCache` for model caching
4. **Error handler registration** β€” `validation_exception_handler` for 422 errors
5. **Route connection** β€” `app.include_router(router)`
### 3.2. Configuration Module (`kiro/config.py`)
Centralized storage of all settings:
| Parameter | Description | Default Value |
|-----------|-------------|---------------|
| `PROXY_API_KEY` | API key for proxy access | `changeme_proxy_secret` |
| `REFRESH_TOKEN` | Kiro refresh token | from `.env` |
| `PROFILE_ARN` | AWS CodeWhisperer profile ARN | from `.env` |
| `REGION` | AWS region | `us-east-1` |
| `KIRO_CREDS_FILE` | Path to JSON credentials file | from `.env` |
| `TOKEN_REFRESH_THRESHOLD` | Time before token refresh | 600 sec (10 min) |
| `MAX_RETRIES` | Max retry attempts | 3 |
| `BASE_RETRY_DELAY` | Base retry delay | 1.0 sec |
| `MODEL_CACHE_TTL` | Model cache TTL | 3600 sec (1 hour) |
| `DEFAULT_MAX_INPUT_TOKENS` | Default max input tokens | 200000 |
| `TOOL_DESCRIPTION_MAX_LENGTH` | Max tool description length | 10000 characters |
| `DEBUG_LAST_REQUEST` | Enable debug logging | `false` |
| `DEBUG_DIR` | Debug logs directory | `debug_logs` |
| `APP_VERSION` | Application version | `0.0.0` |
**Helper functions:**
- `get_kiro_refresh_url(region)` β€” URL for token refresh
- `get_kiro_api_host(region)` β€” main API host
- `get_kiro_q_host(region)` β€” Q API host
- `get_internal_model_id(external_model)` β€” model name conversion
### 3.3. Pydantic Models (`kiro/models_openai.py`)
#### Models for `/v1/models`
| Model | Description |
|-------|-------------|
| `OpenAIModel` | AI model description (id, object, created, owned_by) |
| `ModelList` | Model list for endpoint response |
#### Models for `/v1/chat/completions`
| Model | Description |
|-------|-------------|
| `ChatMessage` | Chat message (role, content, tool_calls, tool_call_id) |
| `ToolFunction` | Tool function description (name, description, parameters) |
| `Tool` | OpenAI format tool (type, function) |
| `ChatCompletionRequest` | Generation request (model, messages, stream, tools, ...) |
#### Response Models
| Model | Description |
|-------|-------------|
| `ChatCompletionChoice` | Single response variant |
| `ChatCompletionUsage` | Token information (prompt_tokens, completion_tokens, credits_used) |
| `ChatCompletionResponse` | Full response (non-streaming) |
| `ChatCompletionChunk` | Streaming chunk |
| `ChatCompletionChunkDelta` | Delta changes in chunk |
| `ChatCompletionChunkChoice` | Variant in streaming chunk |
### 3.4. State Management Layer
#### KiroAuthManager (`kiro/auth.py`)
**Role:** Stateful singleton encapsulating Kiro token management logic.
**Capabilities:**
- Loading credentials from `.env` or JSON file
- Support for `expiresAt` to check token expiration time
- Automatic token refresh 10 minutes before expiration
- Saving updated tokens back to JSON file
- Support for different AWS regions
- Unique fingerprint generation for User-Agent
**Concurrency Control:** Uses `asyncio.Lock` to protect against race conditions.
**Main methods:**
- `get_access_token()` β€” returns valid token, refreshing if necessary
- `force_refresh()` β€” forced token refresh (on 403)
- `is_token_expiring_soon()` β€” expiration time check
**Properties:**
- `profile_arn` β€” profile ARN
- `region` β€” AWS region
- `api_host` β€” API host for region
- `q_host` β€” Q API host for region
- `fingerprint` β€” unique machine fingerprint
```python
# Usage example
auth_manager = KiroAuthManager(
refresh_token="your_token",
region="us-east-1",
creds_file="~/.aws/sso/cache/kiro-auth-token.json"
)
token = await auth_manager.get_access_token()
```
#### ModelInfoCache (`kiro/cache.py`)
**Role:** Thread-safe storage for model configurations.
**Population Strategy:**
- Lazy Loading via `/ListAvailableModels`
- Cache TTL: 1 hour
- Fallback to static model list
**Main methods:**
- `update(models_data)` β€” cache update
- `get(model_id)` β€” get model information
- `get_max_input_tokens(model_id)` β€” get token limit
- `is_empty()` / `is_stale()` β€” cache state check
- `get_all_model_ids()` β€” list of all model IDs
### 3.5. Helper Utilities (`kiro/utils.py`)
| Function | Description |
|----------|-------------|
| `get_machine_fingerprint()` | SHA256 hash of `{hostname}-{username}-kiro-gateway` |
| `get_kiro_headers(auth_manager, token)` | Form headers for Kiro API |
| `generate_completion_id()` | ID in format `chatcmpl-{uuid_hex}` |
| `generate_conversation_id()` | UUID for conversation |
| `generate_tool_call_id()` | ID in format `call_{uuid_hex[:8]}` |
### 3.6. Conversion Layer (`kiro/converters_openai.py`)
#### Message Conversion
OpenAI messages are transformed into Kiro conversationState:
1. **System prompt** β€” added to the first user message
2. **Message history** β€” fully passed in `history` array
3. **Adjacent message merging** β€” messages with the same role are merged
4. **Tool calls** β€” OpenAI tools format support
5. **Tool results** β€” correct transmission of tool call results
#### Long Tool Description Handling
**Problem:** Kiro API returns error 400 for too long descriptions in `toolSpecification.description`.
**Solution:** Tool Documentation Reference Pattern
- If `description ≀ TOOL_DESCRIPTION_MAX_LENGTH` β†’ leave as is
- If `description > TOOL_DESCRIPTION_MAX_LENGTH`:
* In `toolSpecification.description` β†’ reference: `"[Full documentation in system prompt under '## Tool: {name}']"`
* In system prompt, section `"## Tool: {name}"` with full description is added
**Function:** `process_tools_with_long_descriptions(tools)` β†’ `(processed_tools, tool_documentation)`
#### Main Functions
| Function | Description |
|----------|-------------|
| `extract_text_content(content)` | Extract text from various formats |
| `merge_adjacent_messages(messages)` | Merge adjacent messages with same role |
| `build_kiro_history(messages, model_id)` | Build history array for Kiro |
| `build_kiro_payload(request_data, conversation_id, profile_arn)` | Full payload for request |
#### Model Mapping
External model names are converted to internal Kiro IDs:
| External Name | Internal Kiro ID |
|---------------|------------------|
| `claude-opus-4-5` | `claude-opus-4.5` |
| `claude-opus-4-5-20251101` | `claude-opus-4.5` |
| `claude-haiku-4-5` | `claude-haiku-4.5` |
| `claude-haiku-4.5` | `claude-haiku-4.5` (direct passthrough) |
| `claude-sonnet-4-5` | `CLAUDE_SONNET_4_5_20250929_V1_0` |
| `claude-sonnet-4-5-20250929` | `CLAUDE_SONNET_4_5_20250929_V1_0` |
| `claude-sonnet-4` | `CLAUDE_SONNET_4_20250514_V1_0` |
| `claude-sonnet-4-20250514` | `CLAUDE_SONNET_4_20250514_V1_0` |
| `claude-3-7-sonnet-20250219` | `CLAUDE_3_7_SONNET_20250219_V1_0` |
| `auto` | `claude-sonnet-4.5` (alias) |
### 3.7. Parsing Layer (`kiro/parsers.py`)
#### AwsEventStreamParser
Advanced AWS SSE format parser with support for:
- **Bracket counting** β€” correct parsing of nested JSON objects
- **Content deduplication** β€” filtering of duplicate events
- **Tool calls** β€” parsing of structured and bracket-style tool calls
- **Escape sequences** β€” decoding of `\n` and others
#### Event Types
| Event | Description |
|-------|-------------|
| `content` | Text content of the response |
| `tool_start` | Start of tool call (name, toolUseId) |
| `tool_input` | Continuation of input for tool call |
| `tool_stop` | End of tool call |
| `usage` | Credit consumption information |
| `context_usage` | Context usage percentage |
#### Helper Functions
| Function | Description |
|----------|-------------|
| `find_matching_brace(text, start_pos)` | Find closing brace with nesting support |
| `parse_bracket_tool_calls(response_text)` | Parse `[Called func with args: {...}]` |
| `deduplicate_tool_calls(tool_calls)` | Remove duplicate tool calls |
### 3.8. Streaming (`kiro/streaming_openai.py`)
#### stream_kiro_to_openai
Async generator for transforming Kiro stream to OpenAI format.
**Functionality:**
- Parse AWS SSE stream via `AwsEventStreamParser`
- Form OpenAI `chat.completion.chunk`
- Handle tool calls (structured and bracket-style)
- Calculate usage based on `contextUsagePercentage`
- Debug logging via `debug_logger`
#### collect_stream_response
Collects full response from streaming for non-streaming mode.
### 3.9. HTTP Client (`kiro/http_client.py`)
#### KiroHttpClient
Automatic error handling with exponential backoff:
| Error Code | Action |
|------------|--------|
| `403` | Token refresh via `force_refresh()` + retry |
| `429` | Exponential backoff: `BASE_RETRY_DELAY * (2 ** attempt)` |
| `5xx` | Exponential backoff (up to MAX_RETRIES attempts) |
| Timeout | Exponential backoff |
**Delay formula:** `1s, 2s, 4s` (with `BASE_RETRY_DELAY=1.0`)
**Methods:**
- `request_with_retry(method, url, json_data, stream)` β€” request with retry
- `close()` β€” close client
Supports async context manager (`async with`).
### 3.10. Routes (`kiro/routes_openai.py`)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/` | GET | Health check (status, message, version) |
| `/health` | GET | Detailed health check (status, timestamp, version) |
| `/v1/models` | GET | List of available models (requires API key) |
| `/v1/chat/completions` | POST | Chat completions (requires API key) |
**Authentication:** Bearer token in `Authorization` header
### 3.11. Exception Handling (`kiro/exceptions.py`)
| Function | Description |
|----------|-------------|
| `sanitize_validation_errors(errors)` | Convert bytes to strings for JSON serialization |
| `validation_exception_handler(request, exc)` | Pydantic validation error handler (422) |
### 3.12. Debug Logging (`kiro/debug_logger.py`)
**Class:** `DebugLogger` (singleton)
**Activation:** `DEBUG_LAST_REQUEST=true` in `.env`
**Methods:**
| Method | Description |
|--------|-------------|
| `prepare_new_request()` | Clear directory for new request |
| `log_request_body(body)` | Save incoming request |
| `log_kiro_request_body(body)` | Save request to Kiro API |
| `log_raw_chunk(chunk)` | Append raw chunk from Kiro |
| `log_modified_chunk(chunk)` | Append transformed chunk |
**Files in `debug_logs/`:**
- `request_body.json` β€” incoming request (OpenAI format)
- `kiro_request_body.json` β€” request to Kiro API
- `response_stream_raw.txt` β€” raw stream from Kiro
- `response_stream_modified.txt` β€” transformed stream (OpenAI format)
### 3.13. Tokenizer (`kiro/tokenizer.py`)
**Problem:** Kiro API does not return token counts directly. Instead, the API only provides `context_usage_percentage` β€” the percentage of model context usage.
**Solution:** Tokenizer module based on `tiktoken` (OpenAI's Rust library) for fast token counting.
**Features:**
- Uses `cl100k_base` encoding (GPT-4), close to Claude tokenization
- Correction factor `CLAUDE_CORRECTION_FACTOR = 1.15` for improved accuracy
- Lazy initialization for faster imports
- Fallback to rough estimation if tiktoken is unavailable
**Token calculation formula in response:**
```
total_tokens = context_usage_percentage Γ— max_input_tokens (from Kiro API)
completion_tokens = tiktoken(response) (our calculation)
prompt_tokens = total_tokens - completion_tokens (subtraction)
```
**Main functions:**
| Function | Description |
|----------|-------------|
| `count_tokens(text)` | Count tokens in text |
| `count_message_tokens(messages)` | Count tokens in message list |
| `count_tools_tokens(tools)` | Count tokens in tool definitions |
| `estimate_request_tokens(messages, tools)` | Full request token estimation |
**Debug log:**
```
[Usage] claude-opus-4-5: prompt_tokens=142211 (subtraction), completion_tokens=769 (tiktoken), total_tokens=142980 (API Kiro)
```
**Accuracy:** ~97-99.7% compared to API data.
### 3.14. Kiro API Endpoints
All URLs are dynamically formed based on the region:
* **Token Refresh:** `POST https://prod.{region}.auth.desktop.kiro.dev/refreshToken`
* **List Models:** `GET https://q.{region}.amazonaws.com/ListAvailableModels`
* **Generate Response:** `POST https://codewhisperer.{region}.amazonaws.com/generateAssistantResponse`
## 4. Detailed Data Flow
### 4.1 Multi-API Overview
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CLIENTS β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ OpenAI Client β”‚ β”‚ Anthropic Client β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ POST /v1/chat/completions β”‚ POST /v1/messages
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ API LAYER β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ routes_openai.py β”‚ β”‚ routes_anthropic.py β”‚ β”‚
β”‚ β”‚ Security Gate β”‚ β”‚ Security Gate β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚converters_openai.py β”‚ β”‚converters_anthropic β”‚ β”‚
β”‚ β”‚ Extract system β”‚ β”‚ System already β”‚ β”‚
β”‚ β”‚ from messages β”‚ β”‚ separate in request β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CORE LAYER β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ converters_core.py β”‚ β”‚
β”‚ β”‚ build_kiro_payload() β”‚ β”‚
β”‚ β”‚ build_kiro_history() β”‚ β”‚
β”‚ β”‚ process_tools() β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SHARED LAYER β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ KiroAuthManager β”‚ β”‚ KiroHttpClient β”‚ β”‚ ModelInfoCache β”‚ β”‚
β”‚ β”‚ (auth.py) β”‚ β”‚(http_client.py) β”‚ β”‚ (cache.py) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ β”‚ POST /generateAssistantResponse
β”‚ β–Ό
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ Kiro API β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ β”‚ AWS SSE Stream
β”‚ β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ CORE LAYER β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ β”‚ streaming_core.py β”‚ β”‚
β”‚ β”‚ β”‚ parse_kiro_stream() β”‚ β”‚
β”‚ β”‚ β”‚ β†’ KiroEvent objects β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ OUTPUT LAYER β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚streaming_openai.py β”‚ β”‚streaming_anthropic β”‚ β”‚
β”‚ β”‚ format_openai_sse() β”‚ β”‚format_anthropic_sse β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ data: {...} β”‚ β”‚ event: type β”‚ β”‚
β”‚ β”‚ data: [DONE] β”‚ β”‚ data: {...} β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CLIENTS β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ OpenAI Client β”‚ β”‚ Anthropic Client β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
### 4.2 OpenAI API Flow
```
OpenAI Client
β”‚ POST /v1/chat/completions
β–Ό
routes_openai.py ──► converters_openai.py ──► converters_core.py
β”‚ β”‚
β”‚ β–Ό
β”‚ Kiro Payload
β”‚ β”‚
β–Ό β–Ό
KiroAuthManager ──────────────────────────► KiroHttpClient
β”‚
β–Ό
Kiro API
β”‚
β–Ό
streaming_core.py ◄─────────────────────── AWS SSE Stream
β”‚
β–Ό
streaming_openai.py
β”‚
β–Ό
OpenAI SSE Format ──────────────────────► OpenAI Client
```
### 4.3 Anthropic API Flow
```
Anthropic Client
β”‚ POST /v1/messages
β–Ό
routes_anthropic.py ──► converters_anthropic.py ──► converters_core.py
β”‚ β”‚
β”‚ β–Ό
β”‚ Kiro Payload
β”‚ β”‚
β–Ό β–Ό
KiroAuthManager ──────────────────────────────────► KiroHttpClient
β”‚
β–Ό
Kiro API
β”‚
β–Ό
streaming_core.py ◄─────────────────────────────── AWS SSE Stream
β”‚
β–Ό
streaming_anthropic.py
β”‚
β–Ό
Anthropic SSE Format ──────────────────────────► Anthropic Client
```
## 5. Available Models
| Model | Description | Credits |
|-------|-------------|---------|
| `claude-opus-4-5` | Top-tier model | ~2.2 |
| `claude-opus-4-5-20251101` | Top-tier model (version) | ~2.2 |
| `claude-sonnet-4-5` | Enhanced model | ~1.3 |
| `claude-sonnet-4-5-20250929` | Enhanced model (version) | ~1.3 |
| `claude-sonnet-4` | Balanced model | ~1.3 |
| `claude-sonnet-4-20250514` | Balanced (version) | ~1.3 |
| `claude-haiku-4-5` | Fast model | ~0.4 |
| `claude-3-7-sonnet-20250219` | Legacy model | ~1.0 |
## 6. Configuration
### Environment Variables (.env)
```env
# Required
REFRESH_TOKEN="your_kiro_refresh_token"
PROXY_API_KEY="your_proxy_secret"
# Optional
PROFILE_ARN="arn:aws:codewhisperer:..."
KIRO_REGION="us-east-1"
KIRO_CREDS_FILE="~/.aws/sso/cache/kiro-auth-token.json"
# Debug
DEBUG_LAST_REQUEST="false"
DEBUG_DIR="debug_logs"
# Limits
TOOL_DESCRIPTION_MAX_LENGTH="10000"
```
### JSON Credentials File (optional)
```json
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"expiresAt": "2025-01-12T23:00:00.000Z",
"profileArn": "arn:aws:codewhisperer:us-east-1:...",
"region": "us-east-1"
}
```
## 7. API Endpoints
### 7.1 Common Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/` | GET | Health check |
| `/health` | GET | Detailed health check |
### 7.2 OpenAI-compatible Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v1/models` | GET | List of available models |
| `/v1/chat/completions` | POST | Chat completions (streaming/non-streaming) |
**Authentication:** `Authorization: Bearer {PROXY_API_KEY}`
### 7.3 Anthropic-compatible Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v1/messages` | POST | Messages API (streaming/non-streaming) |
**Authentication:** `x-api-key: {PROXY_API_KEY}` + `anthropic-version: 2023-06-01`
### 7.4 Format Comparison
| Aspect | OpenAI | Anthropic |
|--------|--------|-----------|
| System prompt | In `messages` with `role: "system"` | Separate `system` field |
| Content | String or array | Always array of content blocks |
| Stop reason | `finish_reason: "stop"` | `stop_reason: "end_turn"` |
| Usage | `prompt_tokens`, `completion_tokens` | `input_tokens`, `output_tokens` |
| Streaming | `data: {...}\n\n` + `data: [DONE]` | `event: type\ndata: {...}\n\n` |
| Tool format | `{type: "function", function: {...}}` | `{name: "...", input_schema: {...}}` |
## 8. Implementation Features
### Tool Calling
Support for OpenAI-compatible tools format:
```json
{
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
}
}
}
}]
}
```
### Streaming
Full SSE streaming support with correct OpenAI format:
```
data: {"id":"chatcmpl-...","object":"chat.completion.chunk",...}
data: [DONE]
```
### Debugging
When `DEBUG_LAST_REQUEST=true`, all requests and responses are logged in `debug_logs/`:
- `request_body.json` β€” incoming request
- `kiro_request_body.json` β€” request to Kiro API
- `response_stream_raw.txt` β€” raw stream from Kiro
- `response_stream_modified.txt` β€” transformed stream
## 9. Extensibility
### Adding a New API Format
The modular architecture allows easy addition of support for other API formats. Thanks to the Core Layer, most of the logic is already implemented.
#### Steps to Add a New Format (e.g., Gemini)
1. **Create models** β€” `models_gemini.py`
```python
class GeminiRequest(BaseModel):
"""Pydantic model for Gemini request."""
contents: List[GeminiContent]
...
```
2. **Create conversion adapter** β€” `converters_gemini.py`
```python
from kiro.converters_core import build_kiro_payload
def gemini_to_kiro(request: GeminiRequest, ...) -> dict:
"""Converts Gemini request to Kiro payload."""
# Extract data from Gemini format
system_prompt = extract_system_instruction(request)
messages = convert_gemini_contents(request.contents)
tools = convert_gemini_tools(request.tools)
# Use shared core
return build_kiro_payload(
messages=messages,
system_prompt=system_prompt,
tools=tools,
...
)
```
3. **Create streaming formatter** β€” `streaming_gemini.py`
```python
from kiro.streaming_core import parse_kiro_stream
async def stream_to_gemini(response, ...) -> AsyncGenerator[str, None]:
"""Formats Kiro events to Gemini SSE."""
async for event in parse_kiro_stream(response):
yield format_gemini_chunk(event)
```
4. **Create routes** β€” `routes_gemini.py`
```python
router = APIRouter()
@router.post("/v1beta/models/{model}:generateContent")
async def generate_content(request: GeminiRequest):
...
```
5. **Connect in main.py**
```python
from kiro.routes_gemini import router as gemini_router
app.include_router(gemini_router)
```
### What Gets Reused Automatically
When adding a new format, the following components work out of the box:
| Component | Functionality |
|-----------|---------------|
| `auth.py` | Kiro token management |
| `http_client.py` | HTTP with retry logic |
| `cache.py` | Model cache |
| `parsers.py` | AWS SSE parsing |
| `tokenizer.py` | Token counting |
| `converters_core.py` | Kiro payload building |
| `streaming_core.py` | Kiro stream parsing |
## 10. Dependencies
Main project dependencies (from `requirements.txt`):
| Package | Purpose |
|---------|---------|
| `fastapi` | Asynchronous web framework |
| `uvicorn` | ASGI server |
| `httpx` | Asynchronous HTTP client |
| `pydantic` | Data validation and models |
| `python-dotenv` | Environment variable loading |
| `loguru` | Advanced logging |
| `tiktoken` | Fast token counting |