- Add core agent architecture with Command + Skill pattern - Implement Claude API integration for content analysis - Add Obsidian REST API integration for vault operations - Create conversational interface (v2.0) with natural language processing - Add comprehensive configuration management and validation - Include project documentation and developer guides - Set up testing framework with unit, integration, and property tests - Add Kiro specs for Claude API configuration and code quality improvements - Configure project steering files for development guidelines
16 KiB
Design Document
Overview
This design enhances the Claude API configuration system in the Obsidian journal organizer to support custom API URLs and comprehensive model selection. The enhancement maintains backward compatibility while providing flexibility for users to connect to different Claude API endpoints, including proxy servers, regional endpoints, and custom deployments. The design follows the existing configuration patterns and integrates seamlessly with the current Command + Skill architecture.
Architecture
The configuration enhancement will extend the existing YAML-based configuration system with:
- Flexible API URL Configuration: Support for custom Claude API endpoints
- Enhanced Model Validation: Comprehensive model name validation and suggestions
- Environment Variable Integration: Full support for environment-based configuration
- Backward Compatibility Layer: Seamless migration from existing configurations
- Configuration Validation Framework: Robust validation with helpful error messages
Components and Interfaces
Enhanced Claude Configuration Schema
from typing import Optional, Dict, Any, List
from pydantic import BaseModel, validator, Field
import re
from urllib.parse import urlparse
class ClaudeAPIConfig(BaseModel):
"""Enhanced Claude API configuration with URL and model validation"""
api_key: str = Field(..., min_length=1, description="Claude API key")
api_url: Optional[str] = Field(
default="https://api.anthropic.com",
description="Claude API base URL"
)
model: str = Field(
default="claude-3-5-sonnet-20241022",
description="Claude model name"
)
max_tokens: int = Field(default=4096, ge=1, le=200000)
temperature: float = Field(default=0.7, ge=0.0, le=1.0)
@validator('api_url')
def validate_api_url(cls, v: Optional[str]) -> str:
"""Validate API URL format and accessibility"""
if v is None:
return "https://api.anthropic.com"
# Parse URL to validate format
parsed = urlparse(v)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid URL format: {v}")
if parsed.scheme not in ['http', 'https']:
raise ValueError(f"URL must use http or https protocol: {v}")
# Remove trailing slash for consistency
return v.rstrip('/')
@validator('model')
def validate_model_name(cls, v: str) -> str:
"""Validate Claude model name and provide suggestions"""
valid_models = [
# Claude 3 series
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307",
# Claude 3.5 series
"claude-3-5-sonnet-20241022",
"claude-3-5-haiku-20241022",
# Latest aliases
"claude-3-opus-latest",
"claude-3-sonnet-latest",
"claude-3-haiku-latest",
"claude-3-5-sonnet-latest",
"claude-3-5-haiku-latest"
]
if v not in valid_models:
# Check if it follows Claude naming pattern
claude_pattern = r'^claude-\d+(\.\d+)?-(opus|sonnet|haiku)(-\d{8}|-latest)?$'
if not re.match(claude_pattern, v, re.IGNORECASE):
suggestions = ", ".join(valid_models[:5])
raise ValueError(
f"Invalid model name: {v}. "
f"Valid models include: {suggestions}. "
f"Model names should follow pattern: claude-X-Y-YYYYMMDD or claude-X-Y-latest"
)
return v
Configuration Loading and Environment Variable Support
import os
import re
from typing import Any, Dict
import yaml
class ConfigurationLoader:
"""Enhanced configuration loader with environment variable expansion"""
@staticmethod
def expand_environment_variables(config_dict: Dict[str, Any]) -> Dict[str, Any]:
"""Recursively expand environment variables in configuration"""
def expand_value(value: Any) -> Any:
if isinstance(value, str):
# Handle ${VAR} and ${VAR:-default} patterns
pattern = r'\$\{([^}]+)\}'
def replace_env_var(match):
var_expr = match.group(1)
if ':-' in var_expr:
var_name, default_value = var_expr.split(':-', 1)
return os.getenv(var_name.strip(), default_value)
else:
var_name = var_expr.strip()
env_value = os.getenv(var_name)
if env_value is None:
raise ValueError(
f"Environment variable '{var_name}' is not set. "
f"Please set it or provide a default value using ${{{var_name}:-default}}"
)
return env_value
return re.sub(pattern, replace_env_var, value)
elif isinstance(value, dict):
return {k: expand_value(v) for k, v in value.items()}
elif isinstance(value, list):
return [expand_value(item) for item in value]
else:
return value
return expand_value(config_dict)
@classmethod
def load_config(cls, config_path: str) -> Dict[str, Any]:
"""Load and validate configuration with environment variable expansion"""
try:
with open(config_path, 'r', encoding='utf-8') as f:
config_dict = yaml.safe_load(f)
# Expand environment variables
config_dict = cls.expand_environment_variables(config_dict)
return config_dict
except FileNotFoundError:
raise ConfigurationError(f"Configuration file not found: {config_path}")
except yaml.YAMLError as e:
raise ConfigurationError(f"Invalid YAML in configuration file: {e}")
except ValueError as e:
raise ConfigurationError(f"Environment variable error: {e}")
Enhanced Claude API Client
import aiohttp
import ssl
from typing import Optional, Dict, Any
from contextlib import asynccontextmanager
class ClaudeAPIClient:
"""Enhanced Claude API client with configurable endpoint support"""
def __init__(self, config: ClaudeAPIConfig):
self.config = config
self.base_url = config.api_url
self.api_key = config.api_key
self.model = config.model
@asynccontextmanager
async def create_session(self):
"""Create HTTP session with proper configuration"""
headers = {
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json',
'User-Agent': 'journal-organizer/1.0'
}
# Configure SSL context for custom endpoints
ssl_context = ssl.create_default_context()
if self.base_url.startswith('https://localhost') or 'localhost' in self.base_url:
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
connector = aiohttp.TCPConnector(ssl=ssl_context)
async with aiohttp.ClientSession(
connector=connector,
headers=headers,
timeout=aiohttp.ClientTimeout(total=60)
) as session:
yield session
async def validate_connection(self) -> bool:
"""Validate API connection and credentials"""
try:
async with self.create_session() as session:
# Try a simple API call to validate connection
url = f"{self.base_url}/v1/messages"
payload = {
"model": self.model,
"max_tokens": 10,
"messages": [{"role": "user", "content": "test"}]
}
async with session.post(url, json=payload) as response:
if response.status == 401:
raise APIError("Invalid API key or unauthorized access")
elif response.status == 404:
raise APIError(f"API endpoint not found. Check if {self.base_url} is correct")
elif response.status >= 400:
error_text = await response.text()
raise APIError(f"API error ({response.status}): {error_text}")
return True
except aiohttp.ClientError as e:
raise APIError(f"Connection error to {self.base_url}: {str(e)}")
Data Models
Configuration Migration Support
class ConfigurationMigrator:
"""Handle migration from legacy configuration formats"""
@staticmethod
def migrate_claude_config(config_dict: Dict[str, Any]) -> Dict[str, Any]:
"""Migrate legacy Claude configuration to new format"""
claude_config = config_dict.get('claude', {})
# Add default api_url if not present
if 'api_url' not in claude_config:
claude_config['api_url'] = "https://api.anthropic.com"
# Ensure model has a default value
if 'model' not in claude_config:
claude_config['model'] = "claude-3-5-sonnet-20241022"
# Migrate old model names to new format if needed
model_migrations = {
"claude-3-sonnet": "claude-3-sonnet-20240229",
"claude-3-opus": "claude-3-opus-20240229",
"claude-3-haiku": "claude-3-haiku-20240307"
}
if claude_config['model'] in model_migrations:
old_model = claude_config['model']
new_model = model_migrations[old_model]
claude_config['model'] = new_model
print(f"INFO: Migrated model '{old_model}' to '{new_model}'")
config_dict['claude'] = claude_config
return config_dict
Error Handling
Comprehensive Error Management
class ClaudeConfigurationError(ConfigurationError):
"""Specific error for Claude API configuration issues"""
pass
class ModelValidationError(ClaudeConfigurationError):
"""Error for invalid model names with suggestions"""
def __init__(self, message: str, suggestions: List[str] = None):
super().__init__(message)
self.suggestions = suggestions or []
class APIConnectionError(ClaudeConfigurationError):
"""Error for API connection issues"""
pass
def validate_claude_configuration(config: Dict[str, Any]) -> ClaudeAPIConfig:
"""Validate Claude configuration with helpful error messages"""
try:
claude_config = config.get('claude', {})
return ClaudeAPIConfig(**claude_config)
except ValidationError as e:
# Transform pydantic errors into user-friendly messages
error_messages = []
for error in e.errors():
field = error['loc'][0] if error['loc'] else 'configuration'
message = error['msg']
if field == 'api_url':
error_messages.append(
f"Invalid API URL: {message}. "
f"Please provide a valid HTTP/HTTPS URL like 'https://api.anthropic.com'"
)
elif field == 'model':
error_messages.append(
f"Invalid model name: {message}"
)
else:
error_messages.append(f"Invalid {field}: {message}")
raise ClaudeConfigurationError(
f"Claude configuration errors:\n" + "\n".join(f"- {msg}" for msg in error_messages)
)
Testing Strategy
Dual Testing Approach
The testing strategy combines unit tests for specific functionality with property-based tests for comprehensive validation:
Unit Tests:
- Test configuration loading with various URL formats
- Test model name validation with valid and invalid inputs
- Test environment variable expansion with different patterns
- Test backward compatibility with legacy configurations
- Mock API calls to test connection validation
Property-Based Tests:
- Validate that all valid URL formats are accepted
- Test that model name validation works across all valid model patterns
- Verify environment variable expansion handles all supported formats
- Ensure configuration migration preserves all existing functionality
Testing Framework:
- Use
pytestfor unit testing framework - Use
hypothesisfor property-based testing - Configure tests to run minimum 100 iterations per property test
- Tag each property test with: Feature: claude-api-configuration, Property {number}: {property_text}
Now I need to use the prework tool to analyze the acceptance criteria before writing the correctness properties:
Correctness Properties
A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.
Property 1: Configuration Field Support
For any configuration dictionary containing claude section fields (api_url, model), the system should successfully parse and store these values in the configuration object Validates: Requirements 1.1, 2.1
Property 2: URL Validation and Error Handling
For any URL string provided as api_url, the system should validate proper URL format, reject malformed URLs with specific error messages, and accept valid HTTP/HTTPS URLs Validates: Requirements 1.4, 1.5, 4.1
Property 3: Model Name Validation with Suggestions
For any model name string, the system should validate Claude naming conventions, accept all supported model variants, and provide helpful suggestions when invalid model names are provided Validates: Requirements 2.2, 2.3, 2.4, 4.2
Property 4: Default Value Behavior
For any configuration missing api_url or model fields, the system should use appropriate default values (default Anthropic API URL and sensible default model) without errors Validates: Requirements 1.2, 2.5
Property 5: Custom Configuration Usage
For any valid custom api_url or model specified in configuration, the system should use these values in API client initialization and requests Validates: Requirements 1.3
Property 6: Environment Variable Expansion
For any configuration value containing environment variable references, the system should correctly expand variables for both api_url and model fields, and validate expanded values the same as direct configuration Validates: Requirements 5.1, 5.2, 5.4, 5.5
Property 7: Environment Variable Error Handling
For any undefined environment variable referenced in configuration, the system should provide clear error messages indicating which variables are missing Validates: Requirements 5.3
Property 8: Backward Compatibility
For any legacy configuration format, the system should load successfully, maintain existing functionality, and provide informational messages about new options without breaking changes Validates: Requirements 3.1, 3.2, 3.3, 3.4
Property 9: API Connection Validation
For any API configuration (URL and credentials), the system should validate connectivity when possible and provide clear authentication error messages for invalid credentials Validates: Requirements 4.3, 4.4
Property 10: Comprehensive Error Messaging
For any configuration validation failure, the system should provide helpful error messages with corrective suggestions, appropriate logging, and references to documentation where applicable Validates: Requirements 4.5, 6.3, 6.4
Property-Based Testing Configuration
Each property test will be tagged with comments referencing the design document property and run with sufficient iterations to catch edge cases through randomization. Tests will use the hypothesis library for property-based testing with minimum 100 iterations per test.