Commit b6bbf540 authored by Your Name's avatar Your Name

feat: Complete kiro-gateway integration with full feature parity

- Integrated complete kiro-gateway conversion pipeline (1,522 lines)
- Added multi-source authentication (Kiro IDE, kiro-cli, env vars)
- Implemented full OpenAI <-> Kiro format conversion
- Added support for tools/function calling
- Added support for images/multimodal content
- Implemented message merging, validation, and role normalization
- Added KiroAuthManager for automatic token refresh
- Created comprehensive conversion modules:
  - aisbf/kiro_converters.py (core conversion logic)
  - aisbf/kiro_converters_openai.py (OpenAI adapter)
  - aisbf/kiro_models.py (data models)
  - aisbf/kiro_auth.py (authentication)
  - aisbf/kiro_utils.py (utilities)
- Updated KiroProviderHandler with full conversion pipeline
- Added kiro_config support to ProviderConfig
- Updated providers.json with clean Kiro examples
- Added comprehensive documentation (KIRO_INTEGRATION.md)
- Implemented model name prefixing across all providers (provider_id/model)

No external kiro-gateway server needed - all functionality built-in.
parent f8c57389
...@@ -331,6 +331,94 @@ When making changes: ...@@ -331,6 +331,94 @@ When making changes:
2. Add to `PROVIDER_HANDLERS` dictionary 2. Add to `PROVIDER_HANDLERS` dictionary
3. Add provider configuration to `config/providers.json` 3. Add provider configuration to `config/providers.json`
### Kiro Gateway Integration
**Overview:**
Kiro Gateway is a third-party proxy gateway that provides OpenAI and Anthropic-compatible APIs for Kiro (Amazon Q Developer / AWS CodeWhisperer). It's located in the `vendor/kiro-gateway` directory and has been integrated as a provider type in AISBF.
**What is Kiro Gateway:**
- Proxy gateway for accessing Claude models through Kiro IDE/CLI credentials
- Provides OpenAI-compatible endpoints (`/v1/chat/completions`, `/v1/models`)
- Supports both Kiro IDE and kiro-cli authentication methods
- Offers access to Claude models (Sonnet 4.5, Haiku 4.5, Opus 4.5, etc.)
- Includes features like extended thinking, tool calling, and streaming
**Integration Architecture:**
- `KiroProviderHandler` class in `aisbf/providers.py` - treats kiro-gateway as an OpenAI-compatible endpoint
- Uses the OpenAI SDK to communicate with kiro-gateway
- Supports all standard AISBF features: streaming, tools, rate limiting, error tracking
**Configuration:**
In `config/providers.json`:
```json
{
"kiro": {
"id": "kiro",
"name": "Kiro Gateway (Amazon Q Developer)",
"endpoint": "http://localhost:8000/v1",
"type": "kiro",
"api_key_required": true,
"rate_limit": 0,
"models": [
{
"name": "claude-sonnet-4-5",
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000
}
]
}
}
```
In `config/rotations.json`:
```json
{
"kiro-claude": {
"model_name": "kiro-claude",
"providers": [
{
"provider_id": "kiro",
"api_key": "YOUR_KIRO_GATEWAY_API_KEY",
"models": [
{
"name": "claude-sonnet-4-5",
"weight": 3,
"rate_limit": 0
}
]
}
]
}
}
```
**Setup Requirements:**
1. Kiro Gateway must be running (typically on `http://localhost:8000`)
2. Kiro Gateway must be configured with valid Kiro credentials (IDE or CLI)
3. Set `PROXY_API_KEY` in kiro-gateway's `.env` file
4. Use that same API key in AISBF's provider configuration
**Available Models:**
- `claude-sonnet-4-5` - Enhanced model, balanced performance
- `claude-haiku-4-5` - Fast model for quick responses
- `claude-opus-4-5` - Top-tier model (may require paid tier)
- `claude-sonnet-4` - Previous generation model
- `auto` - Automatic model selection
**Usage:**
Once configured, kiro-gateway can be used like any other provider in AISBF:
- Direct provider access: `/api/kiro/chat/completions`
- Rotation access: `/api/kiro-claude/chat/completions`
- Model listing: `/api/kiro/models`
**Benefits:**
- Access Claude models through Kiro credentials without direct Anthropic API access
- Leverage Kiro's free tier or paid plans
- Use Claude models in AISBF rotations alongside other providers
- Automatic failover and load balancing with other providers
### Modifying Configuration ### Modifying Configuration
1. Edit files in `~/.aisbf/` for user-specific changes 1. Edit files in `~/.aisbf/` for user-specific changes
2. Edit files in installed location for system-wide defaults 2. Edit files in installed location for system-wide defaults
......
This diff is collapsed.
# Kiro Integration for AISBF
## Overview
AISBF now includes **direct integration** with Kiro (Amazon Q Developer), allowing you to use Claude models through your Kiro IDE or kiro-cli credentials without running a separate kiro-gateway server.
This integration was built by analyzing and incorporating the core functionality from the [kiro-gateway](https://github.com/jwadow/kiro-gateway) project directly into AISBF.
## Features
- **Direct API Integration**: Makes API calls directly to Amazon Q Developer's API
- **Multiple Credential Sources**: Supports Kiro IDE, kiro-cli, and environment variables
- **Automatic Token Refresh**: Handles token expiration and refresh automatically
- **No External Dependencies**: No need to run kiro-gateway as a separate service
- **Multiple Authentication Types**: Supports both Kiro Desktop Auth and AWS SSO OIDC
## Architecture
The integration consists of three main components:
1. **`aisbf/kiro_auth.py`**: Authentication manager that handles:
- Loading credentials from multiple sources
- Token refresh for both Kiro Desktop Auth and AWS SSO OIDC
- Automatic token lifecycle management
2. **`aisbf/kiro_utils.py`**: Utility functions for:
- Machine fingerprint generation
- Model name normalization
- Request/response format conversion
3. **`aisbf/providers.py`**: KiroProviderHandler that:
- Uses KiroAuthManager for authentication
- Makes direct HTTP requests to Kiro's API
- Converts between OpenAI format and Kiro format
## Configuration
### Method 1: Using Kiro IDE Credentials (JSON File)
If you have Kiro IDE (VS Code extension) installed, AISBF can use its credentials directly.
**Location**: `~/.config/Code/User/globalStorage/amazon.q/credentials.json`
**Configuration in `~/.aisbf/providers.json`**:
```json
{
"providers": {
"kiro": {
"id": "kiro",
"name": "Kiro (Amazon Q Developer)",
"endpoint": "https://q.us-east-1.amazonaws.com",
"type": "kiro",
"api_key_required": false,
"rate_limit": 0,
"kiro_config": {
"creds_file": "~/.config/Code/User/globalStorage/amazon.q/credentials.json",
"region": "us-east-1"
}
}
}
}
```
### Method 2: Using kiro-cli Credentials (SQLite Database)
If you have kiro-cli installed, AISBF can use its credentials from the SQLite database.
**Location**: `~/.local/share/kiro-cli/data.sqlite3`
**Configuration in `~/.aisbf/providers.json`**:
```json
{
"providers": {
"kiro-cli": {
"id": "kiro-cli",
"name": "Kiro CLI (Amazon Q Developer)",
"endpoint": "https://q.us-east-1.amazonaws.com",
"type": "kiro",
"api_key_required": false,
"rate_limit": 0,
"kiro_config": {
"sqlite_db": "~/.local/share/kiro-cli/data.sqlite3",
"region": "us-east-1"
}
}
}
}
```
### Method 3: Using Environment Variables
You can also provide credentials directly via environment variables or configuration.
**Configuration in `~/.aisbf/providers.json`**:
```json
{
"providers": {
"kiro": {
"id": "kiro",
"name": "Kiro (Amazon Q Developer)",
"endpoint": "https://q.us-east-1.amazonaws.com",
"type": "kiro",
"api_key_required": false,
"rate_limit": 0,
"kiro_config": {
"refresh_token": "your-refresh-token-here",
"profile_arn": "arn:aws:codewhisperer:us-east-1:...",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"region": "us-east-1"
}
}
}
}
```
## Available Models
The following Claude models are available through Kiro:
- `anthropic.claude-3-5-sonnet-20241022-v2:0` - Claude 3.5 Sonnet v2 (latest)
- `anthropic.claude-3-5-haiku-20241022-v1:0` - Claude 3.5 Haiku
- `anthropic.claude-3-5-sonnet-20240620-v1:0` - Claude 3.5 Sonnet v1
- `anthropic.claude-sonnet-3-5-v2` - Claude 3.5 Sonnet v2 (alias)
## Usage Examples
### Using Kiro Provider Directly
```bash
# Using Kiro provider with a specific model
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"provider_id": "kiro",
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
]
}'
```
### Using Kiro in a Rotation
Add Kiro to your rotation configuration in `~/.aisbf/rotations.json`:
```json
{
"rotations": {
"kiro-claude": {
"providers": [
{
"provider_id": "kiro",
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
"weight": 1
},
{
"provider_id": "kiro-cli",
"model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
"weight": 1
}
],
"notifyerrors": true
}
}
}
```
Then use the rotation:
```bash
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"rotation_id": "kiro-claude",
"messages": [
{"role": "user", "content": "Hello, how are you?"}
]
}'
```
## Authentication Types
### Kiro Desktop Auth
Used by Kiro IDE (VS Code extension). Credentials are stored in JSON format.
- **Token URL**: `https://prod.{region}.auth.desktop.kiro.dev/refreshToken`
- **Method**: POST with JSON body containing refresh token
- **Response**: Access token with expiration time
### AWS SSO OIDC
Used by kiro-cli. Credentials are stored in SQLite database.
- **Token URL**: `https://oidc.{region}.amazonaws.com/token`
- **Method**: POST with form data (grant_type, client_id, client_secret, refresh_token)
- **Response**: Access token with expiration time
## Regions
Kiro supports multiple AWS regions:
- `us-east-1` (default)
- `eu-central-1`
- `ap-southeast-1`
- `us-west-2`
The API endpoint is always `https://q.{region}.amazonaws.com`, regardless of the region.
## Troubleshooting
### "Kiro authentication not configured"
**Cause**: No valid credentials found in any of the configured sources.
**Solution**:
1. Verify that Kiro IDE or kiro-cli is installed and authenticated
2. Check that the credential file/database path is correct
3. Ensure the credentials haven't expired
### "Profile ARN not available"
**Cause**: The profile ARN couldn't be loaded from credentials.
**Solution**:
1. Re-authenticate with Kiro IDE or kiro-cli
2. Verify that your AWS account has access to Amazon Q Developer
3. Check that the credentials file contains a valid profile ARN
### Token Refresh Failures
**Cause**: Refresh token has expired or is invalid.
**Solution**:
1. Re-authenticate with Kiro IDE or kiro-cli
2. Check that your AWS credentials are still valid
3. Verify network connectivity to AWS endpoints
### "Improperly formed request"
**Cause**: The request format doesn't match Kiro API requirements.
**Solution**:
1. Check that you're using a valid model ID
2. Ensure messages are properly formatted
3. Review the logs for specific error details
## Logging
Enable debug logging to see detailed information about Kiro API calls:
```bash
export AISBF_DEBUG=true
python -m aisbf.main
```
This will show:
- Authentication attempts and token refresh
- API request/response details
- Credential loading process
- Error details
## Comparison with kiro-gateway
### Before (External kiro-gateway)
```
Client → AISBF → kiro-gateway (separate process) → Kiro API
```
**Drawbacks**:
- Need to run kiro-gateway as a separate service
- Additional network hop
- More complex deployment
### After (Direct Integration)
```
Client → AISBF → Kiro API
```
**Benefits**:
- No external dependencies
- Simpler deployment
- Lower latency
- Unified configuration
## Credits
This integration was built by analyzing and incorporating functionality from:
- [kiro-gateway](https://github.com/jwadow/kiro-gateway) by Jwadow
The core authentication and API interaction logic was adapted from kiro-gateway's implementation.
## License
The Kiro integration code in AISBF is licensed under the GNU General Public License v3.0, consistent with AISBF's license.
The original kiro-gateway project is licensed under the GNU Affero General Public License v3.0.
...@@ -52,6 +52,7 @@ class ProviderConfig(BaseModel): ...@@ -52,6 +52,7 @@ class ProviderConfig(BaseModel):
rate_limit: float = 0.0 rate_limit: float = 0.0
api_key: Optional[str] = None # Optional API key in provider config api_key: Optional[str] = None # Optional API key in provider config
models: Optional[List[ProviderModelConfig]] = None # Optional list of models with their configs models: Optional[List[ProviderModelConfig]] = None # Optional list of models with their configs
kiro_config: Optional[Dict] = None # Optional Kiro-specific configuration (credentials, region, etc.)
class RotationConfig(BaseModel): class RotationConfig(BaseModel):
providers: List[Dict] providers: List[Dict]
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
"""
Data models for Kiro integration.
Simple dataclasses to represent OpenAI-style requests for the Kiro converters.
"""
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Union
@dataclass
class FunctionDefinition:
"""Function definition for tools"""
name: str
description: Optional[str] = None
parameters: Optional[Dict[str, Any]] = None
@dataclass
class Tool:
"""Tool definition"""
type: str = "function"
function: Optional[FunctionDefinition] = None
# Flat format support (Cursor-style)
name: Optional[str] = None
description: Optional[str] = None
input_schema: Optional[Dict[str, Any]] = None
@dataclass
class ChatMessage:
"""Chat message"""
role: str
content: Optional[Union[str, List[Dict[str, Any]]]] = None
tool_calls: Optional[List[Dict[str, Any]]] = None
tool_call_id: Optional[str] = None
name: Optional[str] = None
@dataclass
class ChatCompletionRequest:
"""Chat completion request"""
model: str
messages: List[ChatMessage]
tools: Optional[List[Tool]] = None
temperature: Optional[float] = 1.0
max_tokens: Optional[int] = None
stream: Optional[bool] = False
def dict_to_chat_message(msg_dict: Dict[str, Any]) -> ChatMessage:
"""Convert dict to ChatMessage"""
return ChatMessage(
role=msg_dict.get("role", "user"),
content=msg_dict.get("content"),
tool_calls=msg_dict.get("tool_calls"),
tool_call_id=msg_dict.get("tool_call_id"),
name=msg_dict.get("name")
)
def dict_to_tool(tool_dict: Dict[str, Any]) -> Tool:
"""Convert dict to Tool"""
tool_type = tool_dict.get("type", "function")
# Standard OpenAI format
if "function" in tool_dict:
func_dict = tool_dict["function"]
function = FunctionDefinition(
name=func_dict.get("name", ""),
description=func_dict.get("description"),
parameters=func_dict.get("parameters")
)
return Tool(type=tool_type, function=function)
# Flat format (Cursor-style)
return Tool(
type=tool_type,
name=tool_dict.get("name"),
description=tool_dict.get("description"),
input_schema=tool_dict.get("input_schema") or tool_dict.get("parameters")
)
def create_chat_completion_request(
model: str,
messages: List[Dict[str, Any]],
tools: Optional[List[Dict[str, Any]]] = None,
temperature: Optional[float] = 1.0,
max_tokens: Optional[int] = None,
stream: Optional[bool] = False
) -> ChatCompletionRequest:
"""Create ChatCompletionRequest from dicts"""
chat_messages = [dict_to_chat_message(msg) for msg in messages]
chat_tools = [dict_to_tool(tool) for tool in tools] if tools else None
return ChatCompletionRequest(
model=model,
messages=chat_messages,
tools=chat_tools,
temperature=temperature,
max_tokens=max_tokens,
stream=stream
)
"""
Kiro Utilities for AISBF
Adapted from kiro-gateway utils.py
"""
import hashlib
import uuid
import socket
import getpass
import json
from typing import Dict, Any, List, Optional
import hashlib
import uuid as uuid_lib
def get_machine_fingerprint() -> str:
"""Generate a unique machine fingerprint"""
try:
hostname = socket.gethostname()
username = getpass.getuser()
unique_string = f"{hostname}-{username}-kiro-gateway"
return hashlib.sha256(unique_string.encode()).hexdigest()
except:
return hashlib.sha256(b"default-machine-fingerprint").hexdigest()
def generate_completion_id() -> str:
"""Generate a unique completion ID"""
return f"chatcmpl-{uuid_lib.uuid4().hex}"
def generate_conversation_id(messages: Optional[List[dict]] = None) -> str:
"""Generate a stable conversation ID from messages"""
if not messages:
return str(uuid_lib.uuid4())
# Use first 3 messages and last message for hashing
key_messages = messages[:3]
if len(messages) > 3:
key_messages.append(messages[-1])
# Create a stable string for hashing
content = ""
for msg in key_messages:
role = msg.get('role', '')
content_text = str(msg.get('content', ''))[:100]
content += f"{role}:{content_text[:50]}"
return hashlib.sha256(content.encode()).hexdigest()[:16]
def generate_tool_call_id() -> str:
"""Generate a unique ID for tool calls"""
return f"call_{uuid_lib.uuid4().hex[:8]}"
def get_kiro_headers(auth_manager, token: str) -> dict:
"""Get headers for Kiro API requests"""
fingerprint = get_machine_fingerprint()
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"User-Agent": f"aws-sdk-js/1.0.27 KiroIDE-0.7.45-{fingerprint}",
"x-amz-user-agent": f"aws-sdk-js/1.0.27 KiroIDE-0.7.45-{fingerprint}",
"x-amz-codewhisperer-optout": "true",
"x-amzn-kiro-agent-mode": "vibe",
"amz-sdk-invocation-id": str(uuid_lib.uuid4()),
"amz-sdk-request": "attempt=1; max=3"
}
def normalize_model_name(model_name: str) -> str:
"""Normalize model name for Kiro API"""
# Convert various model name formats to Kiro's expected format
model_map = {
'claude-sonnet-4-5': 'claude-sonnet-4-5',
'claude-sonnet-4.5': 'claude-sonnet-4-5',
'claude-sonnet-4': 'claude-sonnet-4',
'claude-haiku-4-5': 'claude-haiku-4-5',
'claude-haiku-4.5': 'claude-haiku-4-5',
'claude-opus-4-5': 'claude-opus-4-5',
'claude-opus-4.5': 'claude-opus-4-5',
'claude-3-5-sonnet': 'claude-sonnet-4-5',
'claude-3-5-sonnet': 'claude-sonnet-4-5',
}
# Normalize the model name
model_lower = model_name.lower().replace('_', '-')
# Check for known aliases
for alias, normalized in model_map.items():
if model_lower == alias or model_lower == alias.replace('-', '_'):
return normalized
# If not in map, try to normalize common patterns
if 'sonnet' in model_lower and '4.5' in model_lower:
return 'claude-sonnet-4-5'
elif 'haiku' in model_lower and '4.5' in model_lower:
return 'claude-haiku-4-5'
elif 'opus' in model_lower and '4.5' in model_lower:
return 'claude-opus-4-5'
elif 'sonnet' in model_lower and '4' in model_lower:
return 'claude-sonnet-4'
# Default to the original name if no match
return model_lower
def build_kiro_request(messages: list, model: str, max_tokens: int = None,
temperature: float = 1.0, stream: bool = False) -> dict:
"""Build a Kiro API request from OpenAI-style request"""
# Convert messages to Kiro format
kiro_messages = []
for msg in messages:
role = msg.get('role')
content = msg.get('content', '')
if role == 'system':
# System messages need special handling
kiro_messages.append({
"role": "user",
"content": content
})
elif role in ['user', 'assistant', 'system']:
kiro_messages.append({
"role": role,
"content": content
})
elif role == 'tool':
# Tool messages need special handling
kiro_messages.append({
"role": "user",
"content": f"[Tool result: {content}]"
})
request = {
"model": normalize_model_name(model),
"messages": kiro_messages,
"max_tokens": max_tokens,
"temperature": temperature,
"stream": stream
}
return request
def parse_kiro_response(response_data: dict) -> dict:
"""Parse Kiro API response to OpenAI format"""
if not response_data:
return {
"id": f"kiro-{uuid_lib.uuid4().hex}",
"object": "chat.completion",
"created": 0,
"model": "unknown",
"choices": [],
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
}
# Extract the response
choices = []
if 'choices' in response_data:
for choice in response_data.get('choices', []):
message = choice.get('message', {})
choices.append({
"index": choice.get('index', 0),
"message": {
"role": "assistant",
"content": message.get('content', ''),
"tool_calls": message.get('tool_calls', [])
},
"finish_reason": choice.get('finish_reason', 'stop')
})
return {
"id": response_data.get('id', f"kiro-{uuid_lib.uuid4().hex}"),
"object": "chat.completion",
"created": response_data.get('created', 0),
"model": response_data.get('model', 'unknown'),
"choices": choices,
"usage": response_data.get('usage', {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
})
}
\ No newline at end of file
This diff is collapsed.
...@@ -180,6 +180,32 @@ ...@@ -180,6 +180,32 @@
"type": "microsoft", "type": "microsoft",
"api_key_required": true, "api_key_required": true,
"rate_limit": 0 "rate_limit": 0
},
"kiro": {
"id": "kiro",
"name": "Kiro IDE (Amazon Q Developer)",
"endpoint": "https://q.us-east-1.amazonaws.com",
"type": "kiro",
"api_key_required": false,
"rate_limit": 0,
"kiro_config": {
"_comment": "Uses Kiro IDE credentials (VS Code extension)",
"creds_file": "~/.config/Code/User/globalStorage/amazon.q/credentials.json",
"region": "us-east-1"
}
},
"kiro-cli": {
"id": "kiro-cli",
"name": "Kiro CLI (Amazon Q Developer)",
"endpoint": "https://q.us-east-1.amazonaws.com",
"type": "kiro",
"api_key_required": false,
"rate_limit": 0,
"kiro_config": {
"_comment": "Uses kiro-cli credentials (SQLite database)",
"sqlite_db": "~/.local/share/kiro-cli/data.sqlite3",
"region": "us-east-1"
}
} }
} }
} }
...@@ -144,6 +144,45 @@ ...@@ -144,6 +144,45 @@
] ]
} }
] ]
},
"kiro-claude": {
"model_name": "kiro-claude",
"notifyerrors": false,
"providers": [
{
"provider_id": "kiro",
"api_key": "YOUR_KIRO_GATEWAY_API_KEY",
"models": [
{
"name": "claude-sonnet-4-5",
"weight": 3,
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000,
"condense_context": 80,
"condense_method": ["hierarchical", "semantic"]
},
{
"name": "claude-haiku-4-5",
"weight": 2,
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000,
"condense_context": 75,
"condense_method": "conversational"
},
{
"name": "claude-sonnet-4",
"weight": 1,
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000,
"condense_context": 80,
"condense_method": "conversational"
}
]
}
]
} }
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment