Commit 406ae38a authored by Your Name's avatar Your Name

Add Claude provider comparison review and include all pending changes

- Add comprehensive comparison of AISBF Claude provider vs original Claude Code source
- Document message conversion, tool handling, streaming, and response parsing differences
- Identify areas for improvement: thinking blocks, tool call streaming, usage metadata
- Include all other pending changes across the codebase
parent f64585f1
This diff is collapsed.
......@@ -292,7 +292,8 @@ class Analytics:
provider_id: Optional[str] = None,
time_range: str = '24h',
from_datetime: Optional[datetime] = None,
to_datetime: Optional[datetime] = None
to_datetime: Optional[datetime] = None,
user_filter: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
Get token usage over time for charts.
......@@ -302,6 +303,7 @@ class Analytics:
time_range: Time range ('1h', '6h', '24h', '7d', '30d', '90d', 'custom')
from_datetime: Optional custom start datetime (used when time_range='custom')
to_datetime: Optional custom end datetime (used when time_range='custom')
user_filter: Optional user ID to filter by
Returns:
List of time-series data points
......@@ -365,26 +367,49 @@ class Analytics:
date_format = "%Y-%m-%d %H:%i"
if provider_id:
cursor.execute(f'''
SELECT
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens
FROM token_usage
WHERE provider_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket
ORDER BY time_bucket
''', (provider_id, cutoff.isoformat(), end_time.isoformat()))
if user_filter:
cursor.execute(f'''
SELECT
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens
FROM token_usage
WHERE provider_id = {placeholder} AND user_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket
ORDER BY time_bucket
''', (provider_id, user_filter, cutoff.isoformat(), end_time.isoformat()))
else:
cursor.execute(f'''
SELECT
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens
FROM token_usage
WHERE provider_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket
ORDER BY time_bucket
''', (provider_id, cutoff.isoformat(), end_time.isoformat()))
else:
cursor.execute(f'''
SELECT
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens,
provider_id
FROM token_usage
WHERE timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket, provider_id
ORDER BY time_bucket
''', (cutoff.isoformat(), end_time.isoformat()))
if user_filter:
cursor.execute(f'''
SELECT
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens,
provider_id
FROM token_usage
WHERE user_id = {placeholder} AND timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket, provider_id
ORDER BY time_bucket
''', (user_filter, cutoff.isoformat(), end_time.isoformat()))
else:
cursor.execute(f'''
SELECT
strftime('{date_format}', timestamp) as time_bucket,
SUM(tokens_used) as tokens,
provider_id
FROM token_usage
WHERE timestamp >= {placeholder} AND timestamp <= {placeholder}
GROUP BY time_bucket, provider_id
ORDER BY time_bucket
''', (cutoff.isoformat(), end_time.isoformat()))
results = []
for row in cursor.fetchall():
......@@ -407,7 +432,8 @@ class Analytics:
provider_filter: Optional[str] = None,
model_filter: Optional[str] = None,
rotation_filter: Optional[str] = None,
autoselect_filter: Optional[str] = None
autoselect_filter: Optional[str] = None,
user_filter: Optional[int] = None
) -> List[Dict[str, Any]]:
"""
Get model performance comparison with optional filters.
......@@ -417,11 +443,12 @@ class Analytics:
model_filter: Optional model name to filter by
rotation_filter: Optional rotation ID to filter by
autoselect_filter: Optional autoselect ID to filter by
user_filter: Optional user ID to filter by
Returns:
List of model performance data
"""
context_dims = self.db.get_all_context_dimensions()
context_dims = self.db.get_all_context_dimensions(user_filter=user_filter)
results = []
for dim in context_dims:
......
......@@ -624,15 +624,22 @@ class DatabaseManager:
if deleted > 0:
logger.info(f"Cleaned up {deleted} old token usage records")
def get_all_context_dimensions(self) -> List[Dict]:
def get_all_context_dimensions(self, user_filter: Optional[int] = None) -> List[Dict]:
"""
Get all context dimension configurations.
Args:
user_filter: Optional user ID to filter by
Returns:
List of dictionaries with context configurations
"""
with self._get_connection() as conn:
cursor = conn.cursor()
# Note: context_dimensions table doesn't have user_id, so we can't filter by user
# This method returns all context dimensions regardless of user_filter
# User-specific filtering happens at the token_usage level in other methods
cursor.execute('''
SELECT provider_id, model_name, context_size, condense_context, condense_method, effective_context, last_updated
FROM context_dimensions
......
This diff is collapsed.
This diff is collapsed.
# Claude Provider Comparison: AISBF vs Original Claude Code Source
**Date:** 2026-03-31
**Reviewed by:** AI Assistant
**AISBF File:** [`aisbf/providers.py`](aisbf/providers.py:2300)
**Original Source:** `vendors/claude/src/`
---
## Overview
This document compares the [`ClaudeProviderHandler`](aisbf/providers.py:2300) implementation in AISBF with the original Claude Code TypeScript source code found in `vendors/claude/src/`.
---
## 1. Architecture & Approach
| Aspect | AISBF Implementation | Original Claude Code |
|--------|---------------------|---------------------|
| **Language** | Python | TypeScript/React |
| **API Method** | Direct HTTP via `httpx.AsyncClient` | Anthropic SDK + internal `callModel` |
| **Authentication** | OAuth2 via `ClaudeAuth` class | Internal OAuth2 + session management |
| **Endpoint** | `https://api.anthropic.com/v1/messages` | Internal SDK routing |
**Assessment:** AISBF correctly uses the direct HTTP approach (kilocode method) which is appropriate for OAuth2 tokens. This matches the pattern used in the original's internal API layer.
---
## 2. Message Format Conversion
**Method:** [`_convert_messages_to_anthropic()`](aisbf/providers.py:2516)
### What AISBF does well:
- Correctly extracts system messages to separate `system` parameter
- Handles tool messages by converting to `tool_result` content blocks
- Converts assistant `tool_calls` to Anthropic `tool_use` blocks
- Handles message role alternation requirements
### Differences from original:
The original ([`normalizeMessagesForAPI()`](vendors/claude/src/utils/messages.ts)) has more sophisticated handling including:
- Thinking block preservation rules
- Protected thinking block signatures
- More complex tool result merging
- Message UUID tracking for caching
---
## 3. Tool Conversion
**Method:** [`_convert_tools_to_anthropic()`](aisbf/providers.py:2419)
### What AISBF does well:
- Correctly converts OpenAI `parameters` → Anthropic `input_schema`
- Normalizes JSON Schema types (e.g., `["string", "null"]``"string"`)
- Removes `additionalProperties: false` (Anthropic doesn't need it)
- Recursively normalizes nested schemas
### Missing from original:
The original has additional tool validation including:
- Tool name length limits
- Parameter size limits
- Schema validation against Anthropic's stricter requirements
- Tool result size budgeting (`applyToolResultBudget` in [`query.ts:379`](vendors/claude/src/query.ts:379))
---
## 4. Tool Choice Conversion
**Method:** [`_convert_tool_choice_to_anthropic()`](aisbf/providers.py:2367)
### Correctly handles:
- `"auto"``{"type": "auto"}`
- `"required"``{"type": "any"}`
- Specific function → `{"type": "tool", "name": "..."}`
### Missing:
- The original has more nuanced tool choice handling including `disable_parallel_tool_use` support
---
## 5. Streaming Implementation
**Method:** [`_handle_streaming_request()`](aisbf/providers.py:2800)
### What AISBF does:
- Uses SSE format parsing (`data:` prefixed lines)
- Handles `content_block_delta` events with `text_delta`
- Handles `message_stop` for final chunk
- Yields OpenAI-compatible chunks
### Differences from original:
The original's streaming ([`callModel()`](vendors/claude/src/query.ts:659)) is more complex:
- Handles thinking blocks during streaming
- Has streaming tool executor (`StreamingToolExecutor`)
- Supports fallback model switching mid-stream
- Has token budget tracking during streaming
- Handles `tool_use` blocks during streaming (not just text)
- Has message backfill for tool inputs
### Missing features in AISBF streaming:
- No thinking block handling
- No tool call streaming
- No fallback model support
- No token budget tracking
---
## 6. Response Conversion
**Method:** [`_convert_to_openai_format()`](aisbf/providers.py:2916)
### Correctly handles:
- Text content extraction
- `tool_use` → OpenAI `tool_calls` format
- Stop reason mapping (`end_turn``stop`, etc.)
- Usage metadata extraction
### Differences:
Original has more complex response handling including:
- Thinking block preservation
- Protected thinking signatures
- More detailed usage tracking (cache tokens, etc.)
---
## 7. Headers & Authentication
**Method:** [`_get_auth_headers()`](aisbf/providers.py:2331)
### AISBF includes:
- OAuth2 Bearer token
- `Anthropic-Version: 2023-06-01`
- `Anthropic-Beta` with multiple beta features
- `X-App: cli` and other stainless headers
**Assessment:** Headers match the original's CLI proxy pattern well. The beta features list (`claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05`) is comprehensive.
---
## 8. Rate Limiting
### AISBF has:
- Adaptive rate limiter with 429 learning
- Exponential backoff with jitter
- Provider disable/enable cycles
### Original has:
- Similar rate limiting but integrated with the query loop
- Model fallback on rate limits
- More sophisticated retry logic
---
## 9. Model Discovery
**Method:** [`get_models()`](aisbf/providers.py:3217)
### AISBF approach:
1. Primary API call to `https://api.anthropic.com/v1/models`
2. Fallback to `http://lisa.nexlab.net:5000/claude/models`
3. Local cache (24-hour TTL)
4. Static fallback list
**Assessment:** Good fallback strategy. The original doesn't have a models endpoint (Anthropic doesn't provide one publicly), so the fallback strategy is appropriate.
---
## 10. Missing Features (Not in AISBF)
The original Claude Code has many features that are out of scope for a provider handler but worth noting:
| Feature | Description | Location in Original |
|---------|-------------|---------------------|
| **Query loop** | Multi-turn tool execution with auto-compact, reactive compact, context collapse | [`query.ts:219`](vendors/claude/src/query.ts:219) |
| **Token budgeting** | Per-turn output token limits with continuation nudges | [`query.ts:1308`](vendors/claude/src/query.ts:1308) |
| **Auto-compaction** | Automatic conversation summarization when context gets large | [`query.ts:454`](vendors/claude/src/query.ts:454) |
| **Context collapse** | Granular context compression | [`query.ts:440`](vendors/claude/src/query.ts:440) |
| **Stop hooks** | Pre/post-turn hook execution | [`query.ts:1267`](vendors/claude/src/query.ts:1267) |
| **Memory prefetch** | Relevant memory file preloading | [`query.ts:301`](vendors/claude/src/query.ts:301) |
| **Skill discovery** | Dynamic skill file detection | [`query.ts:331`](vendors/claude/src/query.ts:331) |
| **Streaming tool execution** | Parallel tool execution during streaming | [`query.ts:1380`](vendors/claude/src/query.ts:1380) |
| **Model fallback** | Automatic fallback to alternative models | [`query.ts:894`](vendors/claude/src/query.ts:894) |
| **Task budget** | Agentic turn budget management | [`query.ts:291`](vendors/claude/src/query.ts:291) |
---
## Summary
### Strengths of AISBF implementation:
1. Clean OAuth2 integration matching kilocode patterns
2. Comprehensive message format conversion
3. Good tool schema normalization
4. Proper streaming SSE handling
5. Robust fallback strategy for model discovery
6. Adaptive rate limiting with learning
### Areas for improvement:
1. Add thinking block support for models that use it
2. Add tool call streaming
3. Add more detailed usage metadata (cache tokens)
4. Consider adding model fallback support
5. Add tool result size validation
### Overall assessment:
The AISBF Claude provider is a solid implementation that correctly handles the core API communication, message conversion, and tool handling. It appropriately focuses on the provider-level concerns (API translation) while leaving higher-level concerns (conversation management, compaction) to the rest of the framework.
......@@ -1135,7 +1135,8 @@ async def dashboard_analytics(
provider_filter: Optional[str] = Query(None),
model_filter: Optional[str] = Query(None),
rotation_filter: Optional[str] = Query(None),
autoselect_filter: Optional[str] = Query(None)
autoselect_filter: Optional[str] = Query(None),
user_filter: Optional[int] = Query(None)
):
"""Token usage analytics dashboard"""
auth_check = require_dashboard_auth(request)
......@@ -1169,6 +1170,9 @@ async def dashboard_analytics(
if from_datetime and to_datetime:
time_range = 'custom'
# Get all users for filter dropdown
all_users = db.get_users() if db else []
# Get available providers, models, rotations, and autoselects for filter dropdowns
available_providers = list(config.providers.keys()) if config else []
available_rotations = list(config.rotations.keys()) if config else []
......@@ -1193,7 +1197,8 @@ async def dashboard_analytics(
provider_id=provider_filter,
time_range=time_range,
from_datetime=from_datetime,
to_datetime=to_datetime
to_datetime=to_datetime,
user_filter=user_filter
)
# Get model performance (with optional filters)
......@@ -1201,7 +1206,8 @@ async def dashboard_analytics(
provider_filter=provider_filter,
model_filter=model_filter,
rotation_filter=rotation_filter,
autoselect_filter=autoselect_filter
autoselect_filter=autoselect_filter,
user_filter=user_filter
)
# Get cost overview
......@@ -1233,10 +1239,12 @@ async def dashboard_analytics(
"available_models": available_models,
"available_rotations": available_rotations,
"available_autoselects": available_autoselects,
"available_users": all_users,
"selected_provider": provider_filter,
"selected_model": model_filter,
"selected_rotation": rotation_filter,
"selected_autoselect": autoselect_filter
"selected_autoselect": autoselect_filter,
"selected_user": user_filter
})
@app.get("/dashboard/login", response_class=HTMLResponse)
......
......@@ -77,7 +77,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<!-- Filter by Provider/Model/Rotation/Autoselect -->
<div style="background: #1a1a2e; padding: 20px; border-radius: 8px; margin-bottom: 30px;">
<h3 style="margin-bottom: 15px;">Filter by Provider, Model, Rotation, or Autoselect</h3>
<h3 style="margin-bottom: 15px;">Filter by Provider, Model, Rotation, Autoselect, or User</h3>
<form method="get" action="/dashboard/analytics" style="display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-end;">
<!-- Preserve date filter parameters -->
<input type="hidden" name="time_range" value="{{ selected_time_range }}">
......@@ -124,13 +124,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</select>
</div>
<div style="flex: 1; min-width: 150px;">
<label style="display: block; margin-bottom: 5px; color: #a0a0a0; font-size: 14px;">User</label>
<select name="user_filter" style="width: 100%; padding: 10px; border-radius: 4px; background: #0f3460; color: white; border: 1px solid #2a4a7a;">
<option value="">All Users</option>
{% for user in available_users %}
<option value="{{ user.id }}" {% if selected_user == user.id %}selected{% endif %}>{{ user.username }}{% if user.role == 'admin' %} (admin){% endif %}</option>
{% endfor %}
</select>
</div>
<div>
<button type="submit" style="padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Apply Filters
</button>
</div>
{% if selected_provider or selected_model or selected_rotation or selected_autoselect %}
{% if selected_provider or selected_model or selected_rotation or selected_autoselect or selected_user %}
<div>
<a href="/dashboard/analytics?time_range={{ selected_time_range }}{% if from_date %}&from_date={{ from_date }}{% endif %}{% if to_date %}&to_date={{ to_date }}{% endif %}" style="padding: 10px 20px; background: #7f8c8d; color: white; border: none; border-radius: 4px; text-decoration: none; display: inline-block;">
Clear Filters
......@@ -139,7 +149,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% endif %}
</form>
{% if selected_provider or selected_model or selected_rotation or selected_autoselect %}
{% if selected_provider or selected_model or selected_rotation or selected_autoselect or selected_user %}
<div style="margin-top: 15px; padding: 10px; background: #0f3460; border-radius: 4px;">
<strong style="color: #60a5fa;">Active Filters: </strong>
<span style="color: #e0e0e0;">
......@@ -147,6 +157,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% if selected_model %}{% if selected_provider %} | {% endif %}Model: {{ selected_model }}{% endif %}
{% if selected_rotation %}{% if selected_provider or selected_model %} | {% endif %}Rotation: {{ selected_rotation }}{% endif %}
{% if selected_autoselect %}{% if selected_provider or selected_model or selected_rotation %} | {% endif %}Autoselect: {{ selected_autoselect }}{% endif %}
{% if selected_user %}{% if selected_provider or selected_model or selected_rotation or selected_autoselect %} | {% endif %}User: {% for user in available_users %}{% if user.id == selected_user %}{{ user.username }}{% endif %}{% endfor %}{% endif %}
</span>
</div>
{% endif %}
......
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