Commit 3d925b69 authored by Your Name's avatar Your Name

Add model metadata fields (top_provider, pricing, description,...

Add model metadata fields (top_provider, pricing, description, supported_parameters, architecture) and dashboard Get Models button

- Update providers.py to extract all fields from provider API responses
- Add max_input_tokens support for Claude provider context size
- Add top_provider, pricing, description, supported_parameters, architecture fields
- Update cache functions to save/load new metadata fields
- Update handlers.py to expose new fields in model list response
- Add Get Models button to dashboard
parent b0dfd636
......@@ -4,6 +4,9 @@ venv/
env/
ENV/
# vendors directory
vendors/
# Python cache directories
__pycache__/
*.py[cod]
......@@ -160,4 +163,4 @@ build/
# OS files
.DS_Store
Thumbs.db
\ No newline at end of file
Thumbs.db
Requirement already satisfied: curl_cffi in /home/nextime/aisbf/venv/lib/python3.13/site-packages (0.14.0)
Requirement already satisfied: cffi>=1.12.0 in /home/nextime/aisbf/venv/lib/python3.13/site-packages (from curl_cffi) (2.0.0)
Requirement already satisfied: certifi>=2024.2.2 in /home/nextime/aisbf/venv/lib/python3.13/site-packages (from curl_cffi) (2026.2.25)
Requirement already satisfied: pycparser in /home/nextime/aisbf/venv/lib/python3.13/site-packages (from cffi>=1.12.0->curl_cffi) (3.0)
......@@ -525,6 +525,87 @@ Once configured, kiro-gateway can be used like any other provider in AISBF:
- Use Claude models in AISBF rotations alongside other providers
- Automatic failover and load balancing with other providers
### Claude Code Provider Integration
**Overview:**
Claude Code is an OAuth2-based authentication method for accessing Claude models through the official Anthropic API with subscription-based access. It uses the same authentication flow as the official claude-cli tool.
**What is Claude Code:**
- OAuth2 authentication for Claude API access
- Uses subscription-based access (claude-code)
- Compatible with claude-cli credentials
- Provides access to latest Claude models (3.7 Sonnet, 3.5 Sonnet, 3.5 Haiku, etc.)
- Supports all Claude features: streaming, tools, vision, extended thinking
**Integration Architecture:**
- [`ClaudeAuth`](aisbf/claude_auth.py) class handles OAuth2 PKCE flow
- [`ClaudeProviderHandler`](aisbf/providers.py) in [`aisbf/providers.py`](aisbf/providers.py) manages API requests
- Supports all standard AISBF features: streaming, tools, rate limiting, error tracking
**Configuration:**
**IMPORTANT:** Claude providers use OAuth2 authentication instead of API keys. The `claude_config` object contains authentication settings.
**Claude Provider Configuration:**
```json
{
"claude": {
"id": "claude",
"name": "Claude Code (OAuth2)",
"endpoint": "https://api.anthropic.com/v1",
"type": "claude",
"api_key_required": false,
"rate_limit": 0,
"claude_config": {
"credentials_file": "~/.claude_credentials.json"
},
"models": [
{
"name": "claude-3-7-sonnet-20250219",
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000
}
]
}
}
```
**Claude Configuration Fields (claude_config):**
- `credentials_file`: Path to OAuth2 credentials file (default: `~/.claude_credentials.json`)
**Authentication Flow:**
1. First request triggers OAuth2 flow if no credentials exist
2. Browser opens to https://claude.ai for authentication
3. User logs in with Claude account
4. Authorization code exchanged for access token
5. Credentials saved to file for future use
6. Automatic token refresh when expired
**Setup Requirements:**
1. Claude subscription (claude-code access)
2. Web browser for initial authentication
3. Port 54545 available for OAuth callback
**Available Models:**
- `claude-3-7-sonnet-20250219` - Latest Claude 3.7 Sonnet
- `claude-3-5-sonnet-20241022` - Claude 3.5 Sonnet
- `claude-3-5-haiku-20241022` - Claude 3.5 Haiku (fast)
- `claude-3-opus-20240229` - Claude 3 Opus (top-tier)
**Usage:**
Once configured, claude provider can be used like any other provider in AISBF:
- Direct provider access: `/api/claude/chat/completions`
- Rotation access: `/api/claude-code/chat/completions`
- Model listing: `/api/claude/models`
**Benefits:**
- Access Claude models through OAuth2 subscription
- No need to manage API keys
- Automatic token refresh
- Use Claude models in AISBF rotations alongside other providers
- Automatic failover and load balancing with other providers
### Modifying Configuration
1. Edit files in `~/.aisbf/` for user-specific changes
2. Edit files in installed location for system-wide defaults
......
This diff is collapsed.
# Claude OAuth2 Provider Setup Guide
## Overview
AISBF now supports Claude Code (claude.ai) as a provider using OAuth2 authentication. This implementation mimics the official Claude CLI authentication flow and includes a Chrome extension to handle OAuth2 callbacks when AISBF runs on a remote server.
## Architecture
### Components
1. **ClaudeAuth Class** (`aisbf/claude_auth.py`)
- Handles OAuth2 PKCE flow
- Manages token storage and refresh
- Stores credentials in `~/.claude_credentials.json` by default
2. **ClaudeProviderHandler** (`aisbf/providers.py`)
- Implements the provider interface for Claude API
- Handles authentication header injection
- Supports automatic token refresh
3. **Chrome Extension** (`static/extension/`)
- Intercepts localhost OAuth2 callbacks (port 54545)
- Redirects callbacks to remote AISBF server
- Auto-configures with server URL
4. **Dashboard Integration** (`templates/dashboard/providers.html`)
- Extension detection and installation prompt
- OAuth2 flow initiation
- Authentication status checking
5. **Backend Endpoints** (`main.py`)
- `/dashboard/extension/download` - Download extension ZIP
- `/dashboard/oauth2/callback` - Receive OAuth2 callbacks
- `/dashboard/claude/auth/start` - Start OAuth2 flow
- `/dashboard/claude/auth/complete` - Complete token exchange
- `/dashboard/claude/auth/status` - Check authentication status
## Setup Instructions
### 1. Add Claude Provider to Configuration
Edit `~/.aisbf/providers.json` or use the dashboard:
```json
{
"providers": {
"claude": {
"id": "claude",
"name": "Claude Code (OAuth2)",
"endpoint": "https://api.anthropic.com/v1",
"type": "claude",
"api_key_required": false,
"rate_limit": 0,
"claude_config": {
"credentials_file": "~/.claude_credentials.json"
},
"models": [
{
"name": "claude-3-7-sonnet-20250219",
"context_size": 200000,
"rate_limit": 0
}
]
}
}
}
```
### 2. Install Chrome Extension (For Remote Servers)
If AISBF runs on a remote server (not localhost), you need the OAuth2 redirect extension:
1. **Download Extension**:
- Go to AISBF Dashboard → Providers
- Expand the Claude provider
- Click "Authenticate with Claude"
- If extension is not detected, click "Download Extension"
2. **Install in Chrome**:
- Extract the downloaded ZIP file
- Open Chrome and go to `chrome://extensions/`
- Enable "Developer mode" (toggle in top-right)
- Click "Load unpacked"
- Select the extracted extension folder
3. **Verify Installation**:
- Extension icon should appear in toolbar
- Click "Check Status" in dashboard to verify
### 3. Authenticate with Claude
1. Go to AISBF Dashboard → Providers
2. Expand the Claude provider
3. Click "🔐 Authenticate with Claude"
4. A browser window will open to claude.ai
5. Log in with your Claude account
6. Authorize the application
7. The window will close automatically
8. Dashboard will show "✓ Authentication successful!"
### 4. Use Claude Provider
Once authenticated, you can use Claude models via the API:
```bash
curl -X POST http://your-server:17765/api/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"model": "claude/claude-3-7-sonnet-20250219",
"messages": [
{"role": "user", "content": "Hello, Claude!"}
]
}'
```
## How It Works
### OAuth2 Flow
1. **Initiation**:
- User clicks "Authenticate" in dashboard
- Dashboard calls `/dashboard/claude/auth/start`
- Server generates PKCE challenge and returns OAuth2 URL
- Dashboard opens URL in new window
2. **Authorization**:
- User logs in to claude.ai
- Claude redirects to `http://localhost:54545/callback?code=...`
3. **Callback Interception** (Remote Server):
- Chrome extension intercepts localhost callback
- Extension redirects to `https://your-server/dashboard/oauth2/callback?code=...`
- Server stores code in session
4. **Token Exchange**:
- Dashboard detects window closed
- Calls `/dashboard/claude/auth/complete`
- Server exchanges code for access/refresh tokens
- Tokens saved to credentials file
5. **API Usage**:
- ClaudeProviderHandler loads tokens from file
- Automatically refreshes expired tokens
- Injects Bearer token in API requests
### Extension Configuration
The extension automatically configures itself with your AISBF server URL. It intercepts requests to:
- `http://localhost:54545/*`
- `http://127.0.0.1:54545/*`
And redirects them to:
- `https://your-server/dashboard/oauth2/callback?...`
## Troubleshooting
### Extension Not Detected
**Problem**: Dashboard shows "OAuth2 Redirect Extension Required"
**Solution**:
1. Verify extension is installed in Chrome
2. Check extension is enabled in `chrome://extensions/`
3. Refresh the dashboard page
4. Try clicking "Check Status" button
### Authentication Timeout
**Problem**: "Authentication timeout. Please try again."
**Solution**:
1. Ensure extension is installed and enabled
2. Check browser console for errors
3. Verify server is accessible from browser
4. Try authentication again
### Token Expired
**Problem**: API requests fail with 401 Unauthorized
**Solution**:
1. Click "Check Status" in dashboard
2. If expired, click "Authenticate with Claude" again
3. Tokens are automatically refreshed on API calls
### Credentials File Not Found
**Problem**: "Provider 'claude' credentials not available"
**Solution**:
1. Check credentials file path in provider config
2. Ensure file exists: `ls -la ~/.claude_credentials.json`
3. Re-authenticate if file is missing or corrupted
## Security Considerations
1. **Credentials Storage**:
- Tokens stored in `~/.claude_credentials.json`
- File should have restricted permissions (600)
- Contains access_token, refresh_token, and expiry
2. **Extension Permissions**:
- Extension only intercepts localhost:54545
- Does not access or store any data
- Only redirects OAuth2 callbacks
3. **Token Refresh**:
- Access tokens expire after ~1 hour
- Automatically refreshed using refresh_token
- Refresh tokens are long-lived
## API Compatibility
The Claude provider supports:
- ✅ Chat completions (`/v1/chat/completions`)
- ✅ Streaming responses
- ✅ System messages
- ✅ Multi-turn conversations
- ✅ Tool/function calling
- ✅ Vision (image inputs)
- ❌ Audio transcription (not supported by Claude API)
- ❌ Text-to-speech (not supported by Claude API)
- ❌ Image generation (not supported by Claude API)
## Required Headers
When using Claude provider, the following headers are automatically added:
```
Authorization: Bearer <access_token>
anthropic-version: 2023-06-01
anthropic-beta: claude-code-20250219
Content-Type: application/json
```
## Example Configuration
Complete provider configuration with multiple models:
```json
{
"providers": {
"claude": {
"id": "claude",
"name": "Claude Code",
"endpoint": "https://api.anthropic.com/v1",
"type": "claude",
"api_key_required": false,
"rate_limit": 0,
"default_rate_limit_TPM": 40000,
"default_rate_limit_TPH": 400000,
"default_context_size": 200000,
"claude_config": {
"credentials_file": "~/.claude_credentials.json"
},
"models": [
{
"name": "claude-3-7-sonnet-20250219",
"context_size": 200000,
"rate_limit": 0,
"rate_limit_TPM": 40000,
"rate_limit_TPH": 400000
},
{
"name": "claude-3-5-sonnet-20241022",
"context_size": 200000,
"rate_limit": 0,
"rate_limit_TPM": 40000,
"rate_limit_TPH": 400000
}
]
}
}
}
```
## Files Modified/Created
### New Files
- `aisbf/claude_auth.py` - OAuth2 authentication handler
- `static/extension/manifest.json` - Extension manifest
- `static/extension/background.js` - Extension service worker
- `static/extension/popup.html` - Extension popup UI
- `static/extension/popup.js` - Popup logic
- `static/extension/options.html` - Extension options page
- `static/extension/options.js` - Options logic
- `static/extension/icons/*.svg` - Extension icons
- `static/extension/README.md` - Extension documentation
- `CLAUDE_OAUTH2_SETUP.md` - This guide
### Modified Files
- `aisbf/providers.py` - Added ClaudeProviderHandler
- `aisbf/config.py` - Added claude provider type support
- `main.py` - Added OAuth2 endpoints
- `templates/dashboard/providers.html` - Added OAuth2 UI
- `templates/dashboard/user_providers.html` - Added OAuth2 UI
- `config/providers.json` - Added example configuration
- `AI.PROMPT` - Added Claude provider documentation
## Support
For issues or questions:
1. Check the troubleshooting section above
2. Review extension console logs
3. Check AISBF server logs
4. Verify OAuth2 flow in browser network tab
## References
- Claude API Documentation: https://docs.anthropic.com/
- OAuth2 PKCE Flow: https://oauth.net/2/pkce/
- Chrome Extension Development: https://developer.chrome.com/docs/extensions/
......@@ -10,4 +10,6 @@ recursive-include config *.md
recursive-include aisbf *.py
recursive-include templates *.html
recursive-include templates *.css
recursive-include templates *.js
\ No newline at end of file
recursive-include templates *.js
recursive-include static *.zip
recursive-include static/extension *.js *.json *.html *.md *.png *.svg
\ No newline at end of file
......@@ -36,7 +36,7 @@ Access the dashboard at `http://localhost:17765/dashboard` (default credentials:
- **Provider-Level Defaults**: Set default condensation settings at provider level with cascading fallback logic
- **Effective Context Tracking**: Reports total tokens used (effective_context) for every request
- **Enhanced Context Condensation**: 8 condensation methods including hierarchical, conversational, semantic, algorithmic, sliding window, importance-based, entity-aware, and code-aware condensation
- **Provider-Native Caching**: 50-70% cost reduction using Anthropic `cache_control` and Google Context Caching APIs
- **Provider-Native Caching**: 50-70% cost reduction using Anthropic `cache_control`, Google Context Caching, and OpenAI-compatible APIs (including prompt_cache_key for OpenAI load balancer routing)
- **Response Caching**: 20-30% cache hit rate with semantic deduplication across multiple backends (memory, Redis, SQLite, MySQL)
- **Smart Request Batching**: 15-25% latency reduction by batching similar requests within 100ms window with provider-specific configurations
- **Streaming Response Optimization**: 10-20% memory reduction with chunk pooling, backpressure handling, and provider-specific streaming optimizations for Google and Kiro providers
......@@ -363,6 +363,73 @@ Edit `~/.aisbf/aisbf.json`:
- **Lower Latency**: Redis provides sub-millisecond cache access
- **Scalability**: Distributed Redis supports multiple AISBF instances
### Provider-Native Caching Configuration
AISBF supports provider-native caching for reduced API costs and latency across multiple provider types:
#### Supported Providers
| Provider | Caching Method | Configuration |
|----------|---------------|---------------|
| **OpenAI** | Automatic prefix caching (no code change needed) | Enabled by default for prompts >1024 tokens |
| **DeepSeek** | Automatic in 64-token chunks | Enabled by default |
| **Anthropic** | `cache_control` with `{"type": "ephemeral"}` | Requires `enable_native_caching: true` |
| **OpenRouter** | `cache_control` (wraps Anthropic) | Requires `enable_native_caching: true` |
| **Google** | Context Caching API (`cached_contents.create`) | Requires `enable_native_caching: true` and `cache_ttl` |
#### Configuration Options
Add to provider configuration in `providers.json`:
```json
{
"providers": {
"my_provider": {
"type": "openai",
"endpoint": "https://api.openrouter.ai/v1",
"enable_native_caching": true,
"min_cacheable_tokens": 1024,
"prompt_cache_key": "optional-cache-key-for-load-balancer"
},
"anthropic_provider": {
"type": "anthropic",
"api_key_required": true,
"enable_native_caching": true,
"min_cacheable_tokens": 1024
},
"google_provider": {
"type": "google",
"api_key_required": true,
"enable_native_caching": true,
"cache_ttl": 3600,
"min_cacheable_tokens": 1024
}
}
}
```
#### Configuration Fields
- **`enable_native_caching`**: Enable provider-native caching (boolean, default: false)
- **`min_cacheable_tokens`**: Minimum token count for caching (default: 1024, matches OpenAI)
- **`cache_ttl`**: Cache TTL in seconds for Google Context Caching (optional)
- **`prompt_cache_key`**: Optional cache key for OpenAI's load balancer routing optimization
#### How It Works
1. **Automatic Providers** (OpenAI, DeepSeek): Caching is handled automatically by the provider based on prompt length and prefix matching - no code changes required in AISBF.
2. **Explicit Providers** (Anthropic, OpenRouter, Google): AISBF adds `cache_control` blocks to messages or creates cached content objects after the first request, then reuses them for subsequent requests with similar prefixes.
3. **OpenAI-Compatible**: Custom providers using OpenAI-compatible endpoints (like OpenRouter) support both automatic caching and explicit `cache_control` blocks.
#### Cost Reduction
- **Anthropic**: 50-70% cost reduction with `cache_control` on long conversations
- **Google**: Up to 75% cost reduction with Context Caching API
- **OpenAI**: Automatic savings on repeated prompt prefixes
- **OpenRouter**: Passes through caching benefits from underlying providers
### Provider-Level Defaults
Providers can now define default settings that cascade to all models:
......
This diff is collapsed.
This diff is collapsed.
......@@ -69,6 +69,7 @@ class ProviderConfig(BaseModel):
api_key: Optional[str] = None # Optional API key in provider config
models: Optional[List[ProviderModelConfig]] = None # Optional list of models with their configs
kiro_config: Optional[Dict] = None # Optional Kiro-specific configuration (credentials, region, etc.)
claude_config: Optional[Dict] = None # Optional Claude-specific configuration (credentials file path)
# Default settings for models in this provider
default_rate_limit: Optional[float] = None
default_max_request_tokens: Optional[int] = None
......@@ -80,9 +81,10 @@ class ProviderConfig(BaseModel):
default_condense_method: Optional[Union[str, List[str]]] = None
default_error_cooldown: Optional[int] = None # Default cooldown period in seconds after 3 consecutive failures (default: 300)
# Provider-native caching configuration
enable_native_caching: bool = False # Enable provider-native caching (Anthropic cache_control, Google Context Caching)
enable_native_caching: bool = False # Enable provider-native caching (Anthropic cache_control, Google Context Caching, OpenAI-compatible APIs)
cache_ttl: Optional[int] = None # Cache TTL in seconds for Google Context Caching API
min_cacheable_tokens: Optional[int] = 1000 # Minimum token count for content to be cacheable
min_cacheable_tokens: Optional[int] = 1024 # Minimum token count for content to be cacheable (default matches OpenAI)
prompt_cache_key: Optional[str] = None # Optional cache key for OpenAI's load balancer routing optimization
# Response caching control
enable_response_cache: Optional[bool] = None # Enable/disable response caching for this provider (None = use global default)
......
......@@ -995,8 +995,16 @@ def get_context_config_for_model(
# Try to find model-specific config in provider
if hasattr(provider_config, 'models') and provider_config.models:
for model in provider_config.models:
if model.get('name') == model_name:
model_specific_config = model
# Handle both Pydantic objects and dictionaries
model_name_value = model.name if hasattr(model, 'name') else model.get('name')
if model_name_value == model_name:
# Convert Pydantic object to dict if needed
if hasattr(model, 'model_dump'):
model_specific_config = model.model_dump()
elif hasattr(model, 'dict'):
model_specific_config = model.dict()
else:
model_specific_config = model
break
# Build base config from provider (model-specific > provider defaults)
......
This diff is collapsed.
......@@ -992,6 +992,18 @@ class RequestHandler:
models = await handler.get_models()
# Apply model filter if configured and no models are manually specified
model_filter = getattr(provider_config, 'model_filter', None)
if model_filter and (not provider_config.models or len(provider_config.models) == 0):
import logging
logger = logging.getLogger(__name__)
logger.info(f"Applying model filter '{model_filter}' to provider {provider_id}")
# Filter models whose ID contains the filter word (case-insensitive)
original_count = len(models)
models = [m for m in models if model_filter.lower() in m.id.lower()]
logger.info(f"Model filter applied: {original_count} -> {len(models)} models")
# Enhance model information with context window and capabilities
enhanced_models = []
current_time = int(time_module.time())
......@@ -1012,13 +1024,42 @@ class RequestHandler:
model_config = m
break
# Add context window information
if model_config and hasattr(model_config, 'context_size'):
# Add context window information - use dynamically fetched value unless manually configured
# Priority: manually configured > dynamically fetched > inferred
if model_config and hasattr(model_config, 'context_size') and model_config.context_size:
# Manually configured - use this value
model_dict['context_window'] = model_config.context_size
elif 'context_window' not in model_dict:
# Try to infer from model name or set a default
elif model_dict.get('context_size'):
# Dynamically fetched from provider - use this value
model_dict['context_window'] = model_dict['context_size']
else:
# Fall back to inference
model_dict['context_window'] = self._infer_context_window(model_name, provider_config.type)
# Add context_length for compatibility - same priority order as context_window
if model_config and hasattr(model_config, 'context_size') and model_config.context_size:
model_dict['context_length'] = model_config.context_size
elif model_dict.get('context_size'):
model_dict['context_length'] = model_dict['context_size']
elif model_dict.get('context_length'):
model_dict['context_length'] = model_dict['context_length']
# Add pricing if available (from dynamic fetch)
if model_dict.get('pricing'):
model_dict['pricing'] = model_dict['pricing']
# Add description if available (from dynamic fetch)
if model_dict.get('description'):
model_dict['description'] = model_dict['description']
# Add top_provider info if available (from dynamic fetch)
if model_dict.get('top_provider'):
model_dict['top_provider'] = model_dict['top_provider']
# Add supported_parameters if available (from dynamic fetch)
if model_dict.get('supported_parameters'):
model_dict['supported_parameters'] = model_dict['supported_parameters']
# Add capabilities information
if model_config and hasattr(model_config, 'capabilities'):
model_dict['capabilities'] = model_config.capabilities
......
......@@ -311,15 +311,25 @@ class KiroAuthManager:
json.dump(data, f, indent=2)
def get_auth_headers(self, token: str) -> dict:
"""Get headers for Kiro API requests"""
fingerprint = self._get_machine_fingerprint()
"""Get headers for Kiro API requests - matches kiro-cli format exactly"""
import platform
import sys
# Get system info for User-Agent (matching kiro-cli's format)
os_name = platform.system().lower()
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
# Build User-Agent matching kiro-cli's AWS SDK Rust format
# Format: aws-sdk-rust/{version} os/{os} lang/rust/{version} md/appVersion/{version} app/AmazonQ-For-CLI
# We adapt this to Python: aws-sdk-python/{version} os/{os} lang/python/{version} md/appVersion/{version} app/AmazonQ-For-CLI
user_agent = f"aws-sdk-python/1.0.0 os/{os_name} lang/python/{python_version} md/appVersion/1.0.0 app/AmazonQ-For-CLI"
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",
"User-Agent": user_agent,
"x-amz-user-agent": user_agent,
"x-amz-codewhisperer-optout": "false",
"amz-sdk-invocation-id": str(uuid.uuid4()),
"amz-sdk-request": "attempt=1; max=3"
}
......
This diff is collapsed.
......@@ -170,8 +170,13 @@ def get_max_request_tokens_for_model(
# Then check provider models config
if hasattr(provider_config, 'models') and provider_config.models:
for model in provider_config.models:
if model.get('name') == model_name:
max_tokens = model.get('max_request_tokens')
# Handle both Pydantic objects and dictionaries
model_name_value = model.name if hasattr(model, 'name') else model.get('name')
if model_name_value == model_name:
max_tokens = model.max_request_tokens if hasattr(model, 'max_request_tokens') else model.get('max_request_tokens')
if max_tokens:
logger.info(f"Found max_request_tokens in provider model config: {max_tokens}")
return max_tokens
if max_tokens:
logger.info(f"Found max_request_tokens in provider model config: {max_tokens}")
return max_tokens
......
......@@ -59,6 +59,18 @@ if ! python -m build --version &> /dev/null; then
pip_install build twine
fi
# Build the extension first
echo ""
echo "Building OAuth2 extension..."
if [ -f "static/extension/build.sh" ]; then
cd static/extension
bash build.sh
cd ../..
echo "Extension built successfully"
else
echo "Warning: Extension build script not found, skipping extension build"
fi
# Clean previous builds
echo ""
echo "Cleaning previous build artifacts..."
......
......@@ -16,7 +16,7 @@
"rate_limit": 0,
"enable_native_caching": false,
"cache_ttl": 3600,
"min_cacheable_tokens": 1000,
"min_cacheable_tokens": 1024,
"models": [
{
"name": "gemini-2.0-flash",
......@@ -71,7 +71,7 @@
"rate_limit": 0,
"enable_native_caching": false,
"cache_ttl": null,
"min_cacheable_tokens": 1000
"min_cacheable_tokens": 1024
},
"ollama": {
"id": "ollama",
......@@ -277,6 +277,49 @@
"sqlite_db": "~/.local/share/kiro-cli/data.sqlite3",
"region": "us-east-1"
}
},
"claude": {
"id": "claude",
"name": "Claude Code (OAuth2)",
"endpoint": "https://api.anthropic.com/v1",
"type": "claude",
"api_key_required": false,
"nsfw": false,
"privacy": false,
"rate_limit": 0,
"claude_config": {
"_comment": "Uses OAuth2 authentication flow (claude-cli compatible)",
"credentials_file": "~/.claude_credentials.json"
},
"models": [
{
"name": "claude-3-7-sonnet-20250219",
"nsfw": false,
"privacy": false,
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000,
"capabilities": ["t2t", "vision", "function_calling"]
},
{
"name": "claude-3-5-sonnet-20241022",
"nsfw": false,
"privacy": false,
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000,
"capabilities": ["t2t", "vision", "function_calling"]
},
{
"name": "claude-3-5-haiku-20241022",
"nsfw": false,
"privacy": false,
"rate_limit": 0,
"max_request_tokens": 200000,
"context_size": 200000,
"capabilities": ["t2t", "vision", "function_calling"]
}
]
}
}
}
This diff is collapsed.
......@@ -22,4 +22,6 @@ protobuf>=3.20,<4
markdown
stem
mysql-connector-python
redis
\ No newline at end of file
redis
flask
curl_cffi>=0.5.0 # Optional: For TLS fingerprinting to bypass Cloudflare (Claude OAuth2)
\ No newline at end of file
......@@ -111,6 +111,7 @@ setup(
'aisbf/kiro_models.py',
'aisbf/kiro_parsers.py',
'aisbf/kiro_utils.py',
'aisbf/claude_auth.py',
'aisbf/semantic_classifier.py',
'aisbf/batching.py',
'aisbf/cache.py',
......
# AISBF OAuth2 Relay Extension
A Chrome extension that intercepts localhost OAuth2 callbacks and redirects them to your remote AISBF server.
## Why This Extension?
Many OAuth2 providers (like Claude/Anthropic) lock their redirect URIs to `localhost` or `127.0.0.1`. When AISBF runs on a remote server, it cannot receive these localhost callbacks directly. This extension solves that problem by intercepting the OAuth2 callback in your browser and redirecting it to your remote AISBF server.
## Features
- **Automatic Redirect**: Intercepts `http://localhost:*` and `http://127.0.0.1:*` OAuth2 callbacks
- **Configurable**: Set your remote AISBF server URL, ports, and callback paths
- **Secure**: Only redirects specific OAuth2 callback paths, not all localhost traffic
- **Easy Setup**: Auto-configuration from AISBF dashboard
- **Visual Status**: Badge shows when relay is active
## Installation
### From AISBF Dashboard (Recommended)
1. Open your AISBF dashboard
2. Go to Providers page
3. When configuring a Claude provider, you'll see an extension installation prompt
4. Click "Download Extension" to get the extension files
5. Follow the installation instructions shown in the dashboard
### Manual Installation
1. Download or clone this extension directory
2. Open Chrome and go to `chrome://extensions/`
3. Enable "Developer mode" (toggle in top right)
4. Click "Load unpacked"
5. Select the `static/extension` directory
6. The extension icon should appear in your toolbar
## Configuration
### Automatic Configuration (Recommended)
1. Install the extension
2. Open your AISBF dashboard
3. The dashboard will automatically detect and configure the extension
4. Click "Configure Extension" when prompted
### Manual Configuration
1. Click the extension icon in your toolbar
2. Click "Configure Server"
3. Enter your AISBF server URL (e.g., `https://192.168.1.100:17765`)
4. The extension will automatically intercept OAuth2 callbacks
### Advanced Options
Click the extension icon → "Advanced Options" to configure:
- **Ports to Intercept**: Comma-separated list (default: `54545`)
- **Callback Paths**: One per line (default: `/callback`, `/oauth/callback`, `/auth/callback`)
## How It Works
1. OAuth2 provider redirects to `http://localhost:54545/callback?code=...`
2. Extension intercepts this request before it fails
3. Extension redirects to `https://your-server.com/dashboard/oauth2/callback?code=...`
4. AISBF receives the callback and completes authentication
## Supported Providers
- Claude (Anthropic) - Port 54545
- Any OAuth2 provider that requires localhost redirect URIs
## Security
- Extension only intercepts specific callback paths, not all localhost traffic
- Only accepts configuration from HTTPS sites or localhost
- No data is stored or transmitted except the OAuth2 callback redirect
- Open source - review the code yourself
## Troubleshooting
### Extension Not Working
1. Check that the extension is enabled in `chrome://extensions/`
2. Verify the remote server URL is correct (click extension icon)
3. Check that the port matches your OAuth2 provider (default: 54545)
4. Look for errors in the extension console (chrome://extensions/ → Details → Inspect views: service worker)
### OAuth2 Still Failing
1. Ensure the extension badge shows "ON" (click extension icon)
2. Verify the callback path is in the configured paths
3. Check AISBF server logs for incoming requests
4. Try disabling and re-enabling the extension
### Configuration Not Saving
1. Check Chrome sync is enabled (extension uses chrome.storage.sync)
2. Try using chrome.storage.local instead (modify background.js)
3. Check browser console for errors
## Development
### File Structure
```
static/extension/
├── manifest.json # Extension manifest (v3)
├── background.js # Service worker with redirect logic
├── popup.html # Extension popup UI
├── popup.js # Popup logic
├── options.html # Options page UI
├── options.js # Options page logic
├── icons/ # Extension icons
│ ├── icon16.svg
│ ├── icon48.svg
│ └── icon128.svg
└── README.md # This file
```
### Testing
1. Load extension in developer mode
2. Configure with your AISBF server URL
3. Try authenticating with a Claude provider
4. Check extension console for logs (look for `[AISBF]` prefix)
### Debugging
Enable verbose logging:
1. Open `chrome://extensions/`
2. Find "AISBF OAuth2 Relay"
3. Click "Inspect views: service worker"
4. Console will show all intercepted requests
## License
Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
## Support
For issues or questions:
- Check AISBF documentation
- Review extension console logs
- Open an issue on the AISBF repository
/**
* AISBF OAuth2 Relay - Background Service Worker
*
* This extension intercepts localhost OAuth2 callbacks and redirects them
* to the remote AISBF server. This is necessary because many OAuth2 providers
* (like Claude/Anthropic) lock their redirect URIs to localhost.
*
* Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>
* Licensed under GPL-3.0
*/
// Default configuration
const DEFAULT_CONFIG = {
enabled: true,
remoteServer: '', // Will be set from AISBF dashboard
ports: [54545], // Default OAuth callback ports to intercept
paths: ['/callback', '/oauth/callback', '/auth/callback']
};
// Current configuration
let config = { ...DEFAULT_CONFIG };
// Load configuration from storage
async function loadConfig() {
try {
const result = await chrome.storage.sync.get(['aisbfConfig']);
if (result.aisbfConfig) {
config = { ...DEFAULT_CONFIG, ...result.aisbfConfig };
}
console.log('[AISBF] Configuration loaded:', config);
await updateRules();
} catch (error) {
console.error('[AISBF] Failed to load config:', error);
}
}
// Save configuration to storage
async function saveConfig(newConfig) {
config = { ...DEFAULT_CONFIG, ...newConfig };
try {
await chrome.storage.sync.set({ aisbfConfig: config });
console.log('[AISBF] Configuration saved:', config);
await updateRules();
return true;
} catch (error) {
console.error('[AISBF] Failed to save config:', error);
return false;
}
}
// Generate declarativeNetRequest rules for interception
function generateRules() {
const rules = [];
let ruleId = 1;
if (!config.enabled || !config.remoteServer) {
return rules;
}
// Clean up remote server URL
let remoteBase = config.remoteServer.replace(/\/$/, '');
// Parse remote server URL to check if it's localhost
let remoteUrl;
try {
remoteUrl = new URL(remoteBase);
} catch (e) {
console.error('[AISBF] Invalid remote server URL:', remoteBase);
return rules;
}
// Check if remote server is localhost/127.0.0.1
const isRemoteLocal = remoteUrl.hostname === 'localhost' ||
remoteUrl.hostname === '127.0.0.1' ||
remoteUrl.hostname === '::1';
// If the remote server is on localhost, we don't need to intercept
// The OAuth2 callback can go directly to localhost without redirection
if (isRemoteLocal) {
console.log('[AISBF] Remote server is localhost - no interception needed');
return rules;
}
for (const port of config.ports) {
for (const path of config.paths) {
// Rule for 127.0.0.1
rules.push({
id: ruleId++,
priority: 1,
action: {
type: 'redirect',
redirect: {
regexSubstitution: `${remoteBase}/dashboard/oauth2/callback\\1`
}
},
condition: {
regexFilter: `^http://127\\.0\\.0\\.1:${port}${path.replace(/\//g, '\\/')}(.*)$`,
resourceTypes: ['main_frame']
}
});
// Rule for localhost
rules.push({
id: ruleId++,
priority: 1,
action: {
type: 'redirect',
redirect: {
regexSubstitution: `${remoteBase}/dashboard/oauth2/callback\\1`
}
},
condition: {
regexFilter: `^http://localhost:${port}${path.replace(/\//g, '\\/')}(.*)$`,
resourceTypes: ['main_frame']
}
});
}
}
return rules;
}
// Update declarativeNetRequest rules
async function updateRules() {
try {
// Get existing rules
const existingRules = await chrome.declarativeNetRequest.getDynamicRules();
const existingRuleIds = existingRules.map(rule => rule.id);
// Generate new rules
const newRules = generateRules();
// Update rules
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: existingRuleIds,
addRules: newRules
});
console.log('[AISBF] Rules updated:', newRules.length, 'rules active');
// Update badge to show status
updateBadge(config.enabled && newRules.length > 0);
} catch (error) {
console.error('[AISBF] Failed to update rules:', error);
}
}
// Update extension badge
function updateBadge(active) {
if (active) {
chrome.action.setBadgeText({ text: 'ON' });
chrome.action.setBadgeBackgroundColor({ color: '#4CAF50' });
} else {
chrome.action.setBadgeText({ text: 'OFF' });
chrome.action.setBadgeBackgroundColor({ color: '#9E9E9E' });
}
}
// Handle messages from popup and options page
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('[AISBF] Received message:', message);
switch (message.type) {
case 'GET_CONFIG':
sendResponse({ success: true, config: config });
break;
case 'SET_CONFIG':
saveConfig(message.config).then(success => {
sendResponse({ success });
});
return true; // Will respond asynchronously
case 'TOGGLE_ENABLED':
config.enabled = !config.enabled;
saveConfig(config).then(success => {
sendResponse({ success, enabled: config.enabled });
});
return true;
case 'GET_STATUS':
chrome.declarativeNetRequest.getDynamicRules().then(rules => {
sendResponse({
success: true,
enabled: config.enabled,
rulesCount: rules.length,
remoteServer: config.remoteServer
});
});
return true;
default:
sendResponse({ success: false, error: 'Unknown message type' });
}
});
// Handle external messages from AISBF dashboard
chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
console.log('[AISBF] External message from:', sender.url, message);
// Security: Only accept messages from HTTPS sites or localhost
const senderUrl = new URL(sender.url);
const isSecure = senderUrl.protocol === 'https:' ||
senderUrl.hostname === 'localhost' ||
senderUrl.hostname === '127.0.0.1';
if (!isSecure) {
sendResponse({ success: false, error: 'Insecure origin' });
return;
}
switch (message.type) {
case 'CONFIGURE':
// AISBF dashboard is setting up the extension
const newConfig = {
enabled: true,
remoteServer: message.remoteServer || sender.url.replace(/\/dashboard.*$/, ''),
ports: message.ports || config.ports,
paths: message.paths || config.paths
};
saveConfig(newConfig).then(success => {
sendResponse({ success, config: newConfig });
});
return true;
case 'PING':
sendResponse({ success: true, version: chrome.runtime.getManifest().version });
break;
case 'GET_STATUS':
chrome.declarativeNetRequest.getDynamicRules().then(rules => {
sendResponse({
success: true,
enabled: config.enabled,
rulesCount: rules.length,
remoteServer: config.remoteServer,
version: chrome.runtime.getManifest().version
});
});
return true;
default:
sendResponse({ success: false, error: 'Unknown message type' });
}
});
// Initialize on install
chrome.runtime.onInstalled.addListener(async (details) => {
console.log('[AISBF] Extension installed:', details.reason);
await loadConfig();
if (details.reason === 'install') {
// Open options page on first install
chrome.runtime.openOptionsPage();
}
});
// Initialize on startup
chrome.runtime.onStartup.addListener(async () => {
console.log('[AISBF] Extension started');
await loadConfig();
});
// Initial load
loadConfig();
#!/bin/bash
# Build script for AISBF OAuth2 Redirect Extension
# Creates a packaged ZIP file ready for distribution
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTPUT_DIR="$SCRIPT_DIR/.."
OUTPUT_FILE="$OUTPUT_DIR/aisbf-oauth2-extension.zip"
echo "Building AISBF OAuth2 Redirect Extension..."
echo "Source directory: $SCRIPT_DIR"
echo "Output file: $OUTPUT_FILE"
# Remove old ZIP if it exists
if [ -f "$OUTPUT_FILE" ]; then
echo "Removing old package..."
rm "$OUTPUT_FILE"
fi
# Create ZIP file
echo "Creating package..."
cd "$SCRIPT_DIR"
zip -r "$OUTPUT_FILE" \
manifest.json \
background.js \
content.js \
popup.html \
popup.js \
options.html \
options.js \
README.md \
icons/ \
-x "*.sh" "*.md~" "*~" ".DS_Store"
echo ""
echo "✓ Extension packaged successfully!"
echo "Package location: $OUTPUT_FILE"
echo ""
echo "To install:"
echo "1. Extract the ZIP file"
echo "2. Open Chrome and go to chrome://extensions/"
echo "3. Enable 'Developer mode'"
echo "4. Click 'Load unpacked'"
echo "5. Select the extracted folder"
This diff is collapsed.
This diff is collapsed.
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<defs>
<linearGradient id="grad128" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
</linearGradient>
</defs>
<circle cx="64" cy="64" r="60" fill="url(#grad128)"/>
<path d="M38 64 L54 80 L90 44" stroke="white" stroke-width="8" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64 20 L64 32 M64 96 L64 108 M20 64 L32 64 M96 64 L108 64" stroke="white" stroke-width="4" opacity="0.3" stroke-linecap="round"/>
<circle cx="64" cy="64" r="56" fill="none" stroke="white" stroke-width="2" opacity="0.2"/>
</svg>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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